Fix external STT server lifecycle to ensure single instance and proper cleanup#1829
Fix external STT server lifecycle to ensure single instance and proper cleanup#1829devin-ai-integration[bot] wants to merge 2 commits intomainfrom
Conversation
…r cleanup - Add process verification functions to hypr_host crate: - has_processes_matching(): Check if processes matching a pattern exist - wait_for_processes_to_terminate(): Wait for processes to terminate with timeout - Update supervisor to wait for process cleanup after stopping external STT servers: - wait_for_process_cleanup(): Ensures old process is fully terminated before returning - Waits up to 5 seconds for graceful termination - Falls back to force kill if process doesn't terminate in time - Add tokio dependency with time feature to host crate for async sleep - Fix test to avoid tautological assertion This fixes the race condition where new STT server processes would start before old ones were fully terminated, leading to multiple server instances running simultaneously. Co-Authored-By: yujonglee <[email protected]>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
✅ Deploy Preview for hyprnote ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
- Refactor wait_for_process_cleanup to accept ProcessCleanupDeps struct - ProcessCleanupDeps holds injected functions for process checking, killing, and sleeping - Add production() constructor that uses real hypr_host functions - Add wait_for_process_cleanup_with() that accepts test dependencies Tests added: - test_cleanup_process_terminates_gracefully: Verifies no force kill when process exits - test_cleanup_process_never_terminates: Verifies force kill is called and sleep happens - test_cleanup_process_kill_returns_zero: Verifies no sleep when kill finds no processes This makes the cleanup logic testable without spawning real processes or waiting real time. Co-Authored-By: yujonglee <[email protected]>
cd99174 to
cb527d5
Compare
|
Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it. |
| return false; | ||
| } | ||
|
|
||
| let max_iterations = max_wait_ms / check_interval_ms; |
There was a problem hiding this comment.
Integer division bug when max_wait_ms < check_interval_ms. If max_wait_ms=50 and check_interval_ms=100, then max_iterations=0, causing the loop to never execute and immediately returning without any waiting. This defeats the purpose of the wait function.
Fix: Use ceiling division to ensure at least one iteration:
let max_iterations = (max_wait_ms + check_interval_ms - 1) / check_interval_ms;Or add a minimum check:
let max_iterations = std::cmp::max(1, max_wait_ms / check_interval_ms);| let max_iterations = max_wait_ms / check_interval_ms; | |
| let max_iterations = std::cmp::max(1, max_wait_ms / check_interval_ms); |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
| fn test_has_processes_matching() { | ||
| let has_stt = has_processes_matching(&ProcessMatcher::Sidecar); | ||
| assert!(!has_stt || has_stt); |
There was a problem hiding this comment.
Tautological assertion that always passes. The assertion assert!(!has_stt || has_stt) is logically equivalent to assert!(true) and provides no actual test coverage. This test will never fail regardless of whether has_processes_matching works correctly.
Fix: Either test a specific expected state or remove the test:
// If you expect no STT processes during tests:
assert!(!has_stt, "Expected no STT processes running");
// Or if the test is not meaningful, remove it entirely| fn test_has_processes_matching() { | |
| let has_stt = has_processes_matching(&ProcessMatcher::Sidecar); | |
| assert!(!has_stt || has_stt); | |
| fn test_has_processes_matching() { | |
| let has_stt = has_processes_matching(&ProcessMatcher::Sidecar); | |
| assert!(!has_stt, "Expected no STT processes running during tests"); | |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
|
Closing due to inactivity for more than 30 days. Configure here. |
Fix external STT server lifecycle to prevent multiple instances
Summary
Fixes a race condition in the external STT server lifecycle management that caused multiple server processes to accumulate when switching between models (e.g., ParakeetV2 ↔ ParakeetV3).
Root Cause: When switching models,
stop_all_stt_servers()would wait for the actor to be removed from the registry, but the actual process cleanup happened asynchronously in thepost_stop()hook. This meant new servers could start spawning before old processes were fully terminated.Solution:
wait_for_process_cleanup()function that verifies external STT processes are actually terminated before returning fromstop_stt_server()hypr_hostcrate for process verificationChanges:
crates/host/src/lib.rs: Addedhas_processes_matching()andwait_for_processes_to_terminate()functionsplugins/local-stt/src/server/supervisor.rs:wait_for_process_cleanup()call after stopping external STT serversProcessCleanupDepsstruct for dependency injectionwait_for_process_cleanup_with()that accepts test dependenciescrates/host/Cargo.toml: Added tokio dependency with time feature for async sleepUpdates Since Last Revision
Added unit tests for the cleanup logic using dependency injection:
test_cleanup_process_terminates_gracefully: Verifies no force kill when process exits cleanlytest_cleanup_process_never_terminates: Verifies force kill is called and sleep happens aftertest_cleanup_process_kill_returns_zero: Verifies no sleep when kill finds no processesThe cleanup logic is now testable without spawning real processes or waiting real time.
Review & Testing Checklist for Human
The unit tests verify the cleanup logic in isolation but do NOT test the full lifecycle with real processes. Manual testing is essential.
ps aux | grep sttor Activity Monitor)owhisper/schema.jsonfile has property reordering - confirm these changes are intentional and don't break anythingRecommended Test Plan
Notes
ServerType::External(Am models), notServerType::Internal(Whisper models) since internal servers don't spawn external processesProcessCleanupDepsstruct uses function pointers withPin<Box<Future>>for testability - the type signature is verbose but necessary for async testingSession: https://app.devin.ai/sessions/0d06a16e24d441a087fec63fc1e92a45
Requested by: yujonglee (@yujonglee)