aboutsummaryrefslogtreecommitdiff
path: root/app/backends
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-25 20:10:40 -0400
committerhistoria <historiavg@proton.me>2026-08-25 20:10:40 -0400
commitca3c78ef577f5d8435dd10db0296c3e2fd8e69e2 (patch)
treecff4b84c14d0ac88bc53f2aef63115352d57de90 /app/backends
parent757588321d4c27889be6e8e3c12b75873ad1218d (diff)
downloadtts-audiobook-generator-ca3c78ef577f5d8435dd10db0296c3e2fd8e69e2.tar.gz
feat: stop server and exit after generating
Diffstat (limited to 'app/backends')
-rw-r--r--app/backends/servers.py26
1 files changed, 26 insertions, 0 deletions
diff --git a/app/backends/servers.py b/app/backends/servers.py
index 62eadb5..965d7df 100644
--- a/app/backends/servers.py
+++ b/app/backends/servers.py
@@ -135,6 +135,28 @@ def _pid_alive(pid: int) -> bool:
return True
+def _reap_exited(pid: int) -> bool:
+ """Reap PID when it is our exited child; True when confirmed dead.
+
+ An exited child stays visible to signal-0 probes until its parent waits
+ for it, and the hub never reaps between ``start`` and ``stop`` — so a
+ server that honored SIGTERM would still count as alive and every stop
+ would burn the whole grace period before escalating to SIGKILL. Returns
+ False when the process may still be running or was not our child (the
+ caller then falls back to its own liveness probes).
+ """
+ if not hasattr(os, "waitpid") or not hasattr(os, "WNOHANG"):
+ return False
+ try:
+ waited, _status = os.waitpid(pid, os.WNOHANG)
+ except ChildProcessError:
+ # Not our child (or someone reaped it already): not ours to judge.
+ return False
+ except OSError:
+ return False
+ return waited == pid
+
+
def _kill_pid(pid: int) -> bool:
"""Terminate PID (and its process group on POSIX). Returns True when dead."""
if sys.platform == "win32":
@@ -163,6 +185,10 @@ def _kill_pid(pid: int) -> bool:
except PermissionError:
return False
for _ in range(int(STOP_GRACE_SECONDS * 10)):
+ # Reap first so an exited (zombie) child ends the wait immediately
+ # instead of keeping the killpg(0) probe "alive" until SIGKILL.
+ if _reap_exited(pid):
+ return True
try:
os.killpg(pgid, 0)
except ProcessLookupError: