Hopefully finally tracked down and fix the high memory alert on text based books.

This commit is contained in:
Storm Dragon
2025-12-10 10:40:17 -05:00
parent 9c63a21804
commit 02e772c799

View File

@@ -194,6 +194,8 @@ class BookReader:
# Audio buffering for seamless playback
self.bufferedAudio = None # Pre-generated next paragraph
self.bufferedChapter = -1 # Which chapter this buffer is for
self.bufferedParagraph = -1 # Which paragraph this buffer is for
self.bufferThread = None
self.cancelBuffer = False
self.bufferLock = threading.Lock()
@@ -822,16 +824,42 @@ class BookReader:
gc.collect(generation=0) # Fast collection at 10 seconds
# Don't reset - let it reach 600 for full GC
# Memory watchdog: warn if exceeding 2GB (50% on Pi 4GB)
# Memory watchdog: warn if using 50% of available system memory
try:
import resource
# pylint: disable=no-member
memUsage = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024 # MB
# Get CURRENT memory usage (not peak) from /proc/self/status
currentMemMb = None
try:
with open('/proc/self/status', 'r') as statusFile:
for line in statusFile:
if line.startswith('VmRSS:'):
# VmRSS is in kB
currentMemMb = int(line.split()[1]) / 1024
break
except (FileNotFoundError, IOError):
# Fall back to resource module (gives peak, not current)
import resource
# pylint: disable=no-member
currentMemMb = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
# More aggressive memory warnings and cleanup
if memUsage > 1536: # 1.5GB threshold
# Get total system memory from /proc/meminfo
totalMemMb = 8192 # Default 8GB if can't read
try:
with open('/proc/meminfo', 'r') as meminfoFile:
for line in meminfoFile:
if line.startswith('MemTotal:'):
# MemTotal is in kB
totalMemMb = int(line.split()[1]) / 1024
break
except (FileNotFoundError, IOError):
pass
# Calculate thresholds as percentages of total memory
cleanupThreshold = totalMemMb * 0.4 # 40% triggers cleanup
warningThreshold = totalMemMb * 0.5 # 50% triggers warning
if currentMemMb and currentMemMb > cleanupThreshold:
if not memoryWarningShown:
print(f"Memory usage: {memUsage:.0f}MB - performing aggressive cleanup")
print(f"Memory usage: {currentMemMb:.0f}MB ({currentMemMb/totalMemMb*100:.1f}% of {totalMemMb:.0f}MB) - performing aggressive cleanup")
# Force aggressive cleanup
if hasattr(self, 'parser') and self.parser:
self.parser.cleanup()
@@ -847,7 +875,7 @@ class BookReader:
gc.collect()
gc.collect() # Second pass
if memUsage > 2048 and not memoryWarningShown:
if currentMemMb > warningThreshold and not memoryWarningShown:
memoryWarningShown = True
self.speechEngine.speak("Warning: High memory usage detected. Consider restarting BookStorm soon.")
except Exception as e:
@@ -2303,13 +2331,26 @@ class BookReader:
if not self.brailleOutput.muteVoice:
wavData = None
try:
# Check if we have buffered audio ready
# Check if we have buffered audio ready for THIS paragraph
with self.bufferLock:
if self.bufferedAudio is not None:
# Use pre-generated audio
if (self.bufferedAudio is not None and
self.bufferedChapter == self.currentChapter and
self.bufferedParagraph == self.currentParagraph):
# Use pre-generated audio (matches current position)
wavData = self.bufferedAudio
self.bufferedAudio = None
self.bufferedChapter = -1
self.bufferedParagraph = -1
else:
# Discard stale buffer if present (wrong paragraph)
if self.bufferedAudio is not None:
print(f"Discarding stale buffer (expected ch{self.currentChapter}p{self.currentParagraph}, got ch{self.bufferedChapter}p{self.bufferedParagraph})")
del self.bufferedAudio
self.bufferedAudio = None
self.bufferedChapter = -1
self.bufferedParagraph = -1
# Force GC to reclaim buffer memory immediately
gc.collect()
# Generate audio now (first paragraph or after navigation)
wavData = self.ttsEngine.text_to_wav_data(paragraph)
@@ -2422,12 +2463,17 @@ class BookReader:
return
# CRITICAL: Clear any stale buffered audio before starting new thread
# This happens when buffer thread finishes AFTER we already generated audio synchronously
# This shouldn't normally happen now that we track buffer position
with self.bufferLock:
if self.bufferedAudio is not None:
print("Warning: Discarding stale buffered audio (orphaned buffer)")
# This is now a safety check - should rarely trigger
print(f"Warning: Clearing existing buffer before buffering next (ch{self.bufferedChapter}p{self.bufferedParagraph})")
del self.bufferedAudio
self.bufferedAudio = None
self.bufferedChapter = -1
self.bufferedParagraph = -1
# Force immediate garbage collection
gc.collect()
# Calculate next paragraph position
nextChapter = self.currentChapter
@@ -2464,15 +2510,19 @@ class BookReader:
_wavData_to_cleanup = None
return
# Store buffered audio
# Store buffered audio with position tracking
with self.bufferLock:
if not self.cancelBuffer:
self.bufferedAudio = _wavData_to_cleanup
self.bufferedChapter = nextChapter
self.bufferedParagraph = nextParagraph
_wavData_to_cleanup = None # Transfer ownership
except Exception as e:
print(f"Error buffering paragraph: {e}")
with self.bufferLock:
self.bufferedAudio = None
self.bufferedChapter = -1
self.bufferedParagraph = -1
if _wavData_to_cleanup is not None:
del _wavData_to_cleanup
_wavData_to_cleanup = None
@@ -2508,6 +2558,8 @@ class BookReader:
del self.bufferedAudio
self.bufferedAudio = None
self.cancelBuffer = False
# Force garbage collection if we discarded a buffer
gc.collect()
# Reset thread reference
self.bufferThread = None