Hopefully finally tracked down and fix the high memory alert on text based books.
This commit is contained in:
80
bookstorm.py
80
bookstorm.py
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user