From 1da9ac1facf21ef2ab2c2d407805ca3e42ddbffc Mon Sep 17 00:00:00 2001 From: imonlinux <39863321+imonlinux@users.noreply.github.com> Date: Sat, 10 Jan 2026 13:23:33 -0800 Subject: [PATCH] Update audio_engine.py Summary Add thread-safe locking for wake word reload logic Problem Wake word reload logic accessed self.state.wake_words_changed flag without synchronization, creating a potential race condition when wake words are dynamically switched via Home Assistant. Changes Added self._wake_words_lock = threading.Lock() in __init__ Wrapped wake word reload block with with self._wake_words_lock: context manager Ensures atomic read-modify-write of wake_words_changed flag Impact Risk: Very low - purely defensive improvement Benefit: Eliminates race condition in wake word switching Performance: Negligible overhead (lock only held during reload check) --- linux_voice_assistant/audio_engine.py | 55 +++++++++++++++------------ 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/linux_voice_assistant/audio_engine.py b/linux_voice_assistant/audio_engine.py index 8b23095..7d74ff5 100644 --- a/linux_voice_assistant/audio_engine.py +++ b/linux_voice_assistant/audio_engine.py @@ -27,6 +27,9 @@ class AudioEngine: self.mic = mic self.block_size = block_size self._thread: Optional[threading.Thread] = None + + # CRITICAL FIX: Add lock for thread-safe wake word reload + self._wake_words_lock = threading.Lock() def start(self): """Starts the audio processing thread.""" @@ -113,33 +116,35 @@ class AudioEngine: time.sleep(0.01) continue - # Update active wake words if changed - if (not wake_words) or (self.state.wake_words_changed): - self.state.wake_words_changed = False - wake_words = [ - ww - for ww in self.state.wake_words.values() - if ww.id in self.state.active_wake_words - ] - has_oww = any( - isinstance(ww, OpenWakeWord) for ww in wake_words - ) - - if has_oww and (oww_features is None): - _LOGGER.debug( - "Initializing OpenWakeWord features..." + # CRITICAL FIX: Protect wake word reload with lock + with self._wake_words_lock: + # Update active wake words if changed + if (not wake_words) or (self.state.wake_words_changed): + self.state.wake_words_changed = False + wake_words = [ + ww + for ww in self.state.wake_words.values() + if ww.id in self.state.active_wake_words + ] + has_oww = any( + isinstance(ww, OpenWakeWord) for ww in wake_words ) - oww_features = OpenWakeWordFeatures.from_builtin() - _LOGGER.debug( - "Rebuilt wake_words list: %s (has_oww=%s, active_ids=%s)", - [ - getattr(ww, "wake_word", getattr(ww, "id", "?")) - for ww in wake_words - ], - has_oww, - self.state.active_wake_words, - ) + if has_oww and (oww_features is None): + _LOGGER.debug( + "Initializing OpenWakeWord features..." + ) + oww_features = OpenWakeWordFeatures.from_builtin() + + _LOGGER.debug( + "Rebuilt wake_words list: %s (has_oww=%s, active_ids=%s)", + [ + getattr(ww, "wake_word", getattr(ww, "id", "?")) + for ww in wake_words + ], + has_oww, + self.state.active_wake_words, + ) try: # Feature extraction