Allow OmniVoice slow sidecar startup (#1743)

Fixes #1711.\n\nGives only the OmniVoice subprocess a 120-second readiness budget while retaining the shared 30-second default for all other sidecars, with regression coverage.
This commit is contained in:
Palash Debnath
2026-09-02 00:22:05 +05:30
committed by GitHub
parent 497d57ee62
commit 4e5e8d1f89
4 changed files with 44 additions and 1 deletions
+1
View File
@@ -27,6 +27,7 @@ the frozen-backend fallback mirror it for their toolchains.
### Fixed
- OmniVoice subprocess startup now allows slow packaged Windows Python runtimes to signal readiness before termination (#1711)
- SRT files selected during source analysis now wait for speaker cloning, then replace transcript text without losing voices (#1709)
- Windows MSI deployments can now prohibit WebView2 bootstrap with `DISABLEWEBVIEW2BOOTSTRAP=1`, and `AUTOLAUNCHAPP=0` reliably suppresses first launch (#1714)
- Subtitle rows now provide 100 ms timing steppers and flag adjacent overlaps without requiring precise timeline dragging (#1710)
@@ -52,6 +52,11 @@ class OmniVoiceSubprocessBackend(SubprocessBackend):
# Match OmniVoiceBackend: the measured floor below which a render that
# should take seconds runs for minutes (the #1226/#1222 4 GB reports).
min_vram_gb = 6.0
# Packaged Windows hosts can spend more than the base 30 seconds starting
# the shared Python runtime before this stdlib-only sidecar emits ready.
# Keep the bound below the 300-second generation budget while avoiding the
# repeated false kill captured in #1711.
spawn_ready_timeout_s = 120.0
@classmethod
def is_available(cls) -> tuple[bool, str]:
+2 -1
View File
@@ -363,6 +363,7 @@ class SubprocessBackend(TTSBackend):
# be a different class object from the one the subclass closed over.
# A duck-typed marker survives that.
_is_subprocess_isolated: bool = True
spawn_ready_timeout_s: float = SPAWN_READY_TIMEOUT_S
# Generation happens in the sidecar: parent-side accelerator counters
# can't see its allocations (see TTSBackend.runs_out_of_process).
@@ -524,7 +525,7 @@ class SubprocessBackend(TTSBackend):
# Block on the ready handshake. A sidecar that fails to emit ready
# within SPAWN_READY_TIMEOUT_S is killed and the failure is raised.
try:
frame = self._recv_with_timeout(SPAWN_READY_TIMEOUT_S)
frame = self._recv_with_timeout(self.spawn_ready_timeout_s)
except Exception:
self._force_kill()
raise
@@ -273,6 +273,42 @@ def test_omnivoice_subprocess_recv_timeout_overrides_default():
assert b.recv_timeout_s == 300.0 # aligns with the generate budget
def test_omnivoice_subprocess_has_longer_spawn_budget_than_other_sidecars():
assert _PlainBackend.spawn_ready_timeout_s == 30.0
assert OmniVoiceSubprocessBackend.spawn_ready_timeout_s == 120.0
def test_spawn_uses_backend_specific_ready_timeout(monkeypatch, tmp_path):
_use_stub(monkeypatch, tmp_path / "unused.py")
backend = OmniVoiceSubprocessBackend()
observed = []
class StubProcess:
stderr = io.BytesIO()
@staticmethod
def poll():
return None
monkeypatch.setattr(
"services.subprocess_backend.spawn_owned",
lambda *_args, **_kwargs: StubProcess(),
)
monkeypatch.setattr(
backend,
"_recv_with_timeout",
lambda timeout: observed.append(timeout) or {"op": "ready"},
)
monkeypatch.setattr("services.subprocess_backend._ensure_reaper_running", lambda: None)
try:
backend._spawn()
finally:
backend._proc = None
assert observed == [120.0]
def test_omnivoice_subprocess_recv_timeout_env_override(monkeypatch):
monkeypatch.setenv("OMNIVOICE_SIDECAR_RECV_TIMEOUT_S", "120")
assert OmniVoiceSubprocessBackend().recv_timeout_s == 120.0