From 09a4e34eec470b7ad90d03ee279f71e1089520b2 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 02:48:31 +0000 Subject: [PATCH] perf: Release lock before GC in ParakeetManager Releases the thread lock before calling `gc.collect()` in `_unload_model`. This prevents the garbage collector (which is stop-the-world) from blocking other threads (like `transcribe`) that are waiting to acquire the lock to reload the model. While GC still pauses execution, releasing the lock allows the `transcribe` thread to proceed as soon as the GIL allows (potentially loading the new model in parallel with GC if C++ extensions release GIL), rather than being serialized behind the entire GC operation. Fixes a discrepancy where the code did not match the documented behavior. Co-authored-by: Whamp <1115485+Whamp@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ src/chirp/parakeet_manager.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 96ecba4..efe25c0 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,7 @@ ## 2024-02-18 - Pre-scaled Audio Feedback **Learning:** AudioFeedback volume scaling was applied during every playback, causing unnecessary numpy overhead and latency. **Action:** Pre-calculate scaled audio during `_load_and_cache` to minimize `_play_cached` latency (from ~1ms to ~0.04ms). + +## 2025-05-18 - Unlocked Garbage Collection +**Learning:** `ParakeetManager` held the model lock during `gc.collect()`, forcing concurrent `transcribe` requests (which require the lock) to wait for full GC completion before they could even *start* reloading the model. +**Action:** Release the lock before calling `gc.collect()`. This allows `transcribe` to acquire the lock and begin `load_model` (which releases GIL for C++ ops) in parallel with the garbage collection of the old model. diff --git a/src/chirp/parakeet_manager.py b/src/chirp/parakeet_manager.py index 54667ef..2cc1440 100644 --- a/src/chirp/parakeet_manager.py +++ b/src/chirp/parakeet_manager.py @@ -65,11 +65,15 @@ def _monitor_loop(self) -> None: self._unload_model() def _unload_model(self) -> None: + should_collect = False with self._lock: if self._model is not None and (time.time() - self._last_access > self._timeout): self._logger.info("Unloading Parakeet model to free memory.") self._model = None - gc.collect() + should_collect = True + + if should_collect: + gc.collect() def ensure_loaded(self): with self._lock: