Commit Graph
1 Commits
Author SHA1 Message Date
Palash Debnath b1d86b2c96 fix(tts): a cold model load from a GPU-pool worker no longer waits on the wrong event loop (#1417) (#1418)
`_model_lock` is a module-level asyncio.Lock, so it binds to whichever loop first contends for it — in practice the server's. But OmniVoiceBackend._ensure_loaded() runs on a GPU-pool worker thread with no running loop and bootstraps a fresh one via asyncio.run(get_model()). Awaiting a lock owned by another loop does not block, it raises 'is bound to a different event loop', which reached users as a 500 — or deadlocks, depending on which loop touched it first.

_heal_tts_placement already carried a running_on_gpu_pool() guard for exactly this; the cold-load path never got one. It now loads inline on the calling thread.

The load must run inline rather than through _load_model_with_timeout(), which would hand _load_model_sync back to _get_gpu_pool() — the pool the caller already occupies. MPS pins that pool to a single worker, so it would wait on itself. Exclusion therefore comes from a loop-agnostic threading.Lock, not from holding a GPU slot: a slot is not exclusion when the pool has more than one worker, which CUDA hosts do.

Regression tests drive the real failure shape — a live foreign loop genuinely holding the lock, since an uncontended acquire() never binds — and install a pool that refuses submit, so a re-submission regression fails in under a second instead of hanging the suite.
2026-08-08 03:02:10 +05:30