SubtitleEdit/subtitleedit · error · TimeoutException

qwen3-tts-server did not report healthy within 120s. Last ou

Error message

qwen3-tts-server did not report healthy within 120s. Last output: {lastOutput}

What it means

TimeoutException thrown when the qwen3-tts-server stays alive but never answers the /health probe within 120 seconds. The server is explicitly stopped (StopServerInternal) before throwing, and the last captured stderr/output is appended so the user can see where the server stalled.

Source

Thrown at src/ui/Features/Video/TextToSpeech/Engines/Qwen3TtsCpp.cs:436

                    _serverModelFileName = null;
                    // An empty tail plus a 0xC00000xx code means the loader killed it before main -
                    // NativeExitCodeHelper explains those instead of just printing the raw number.
                    var hint = NativeExitCodeHelper.GetHint(process.ExitCode, "Qwen3 TTS", GetSetFolder());
                    throw new InvalidOperationException(
                        $"qwen3-tts-server exited during startup (code {NativeExitCodeHelper.Describe(process.ExitCode)})."
                        + (hint == null ? string.Empty : " " + hint)
                        + $" Output: {tail}");
                }
                if (await ProbeHealthAsync(port, TimeSpan.FromSeconds(1), ct))
                {
                    return;
                }
                await Task.Delay(TimeSpan.FromMilliseconds(500), ct);
            }

            var lastOutput = SnapshotStderr(stderrBuffer);
            StopServerInternal();
            throw new TimeoutException(
                $"qwen3-tts-server did not report healthy within 120s. Last output: {lastOutput}");
        }
        finally
        {
            ServerLock.Release();
        }
    }

    private static string SnapshotStderr(StringBuilder buffer)
    {
        lock (buffer)
        {
            var s = buffer.ToString().TrimEnd();
            return s.Length > 2000 ? s[^2000..] : s;
        }
    }

    // Tail of the running server's captured stdout/stderr - empty when no server is tracked.

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Read Last output — a server stuck printing init progress is just slow, not broken; a silent log suggests a port/endpoint mismatch.
  2. Confirm no other qwen3-tts-server is already bound to the loopback port (the code picks a free port, but a stale zombie can race).
  3. Warm the model once outside the app (run the server manually) to confirm it eventually becomes healthy, then increase the deadline if the platform legitimately needs longer.
  4. Update ProbeHealthAsync's route if the server build renamed /health.
Defensive patterns

Strategy: retry

Validate before calling

// Confirm the health route exists for this server build before relying on the 120s wait.
var routeOk = await ProbeHealthAsync(port, TimeSpan.FromSeconds(1), ct);

Try / catch

try { await EnsureServerRunningAsync(modelFileName, ct); }
catch (TimeoutException ex)
{
    // Server alive but slow — surface Last output; offer to retry with a warmed model.
    Se.LogError(ex);
    throw;
}

Prevention

When it happens

Trigger: First-run model load on a slow CPU/disk exceeds 120s; the server is alive but bound to the wrong port or didn't open the expected /health route; GPU init is stuck (e.g. waiting on a driver handoff); the model is being paged in over a slow network mount.

Common situations: Cold start of a large model on integrated graphics; the server's /health endpoint changed name in a newer build so ProbeHealthAsync always 404s; another instance of the server already holds the port; very slow disk where mmap-loading the GGUF takes minutes.

Related errors


AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13). Data as JSON: /api/errors/f386b4a3b05b4afe. Report an issue: GitHub.