Skip to content

Commit 6bbb80d

Browse files
committed
feat(subprocess): add a watchdog to exit subprocess when parent dies
1 parent 0298bad commit 6bbb80d

File tree

1 file changed

+33
-2
lines changed

1 file changed

+33
-2
lines changed

python/cocoindex/subprocess_exec.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
import pickle
1717
import threading
1818
import asyncio
19+
import os
20+
import time
1921
from .user_app_loader import load_user_app
2022

23+
WATCHDOG_INTERVAL_SECONDS = 10.0
2124

2225
# ---------------------------------------------
2326
# Main process: single, lazily-created pool
@@ -33,7 +36,9 @@ def _get_pool() -> ProcessPoolExecutor:
3336
if _pool is None:
3437
# Single worker process as requested
3538
_pool = ProcessPoolExecutor(
36-
max_workers=1, initializer=_subprocess_init, initargs=(_user_apps,)
39+
max_workers=1,
40+
initializer=_subprocess_init,
41+
initargs=(_user_apps, os.getpid()),
3742
)
3843
return _pool
3944

@@ -48,7 +53,33 @@ def add_user_app(app_target: str) -> None:
4853
# ---------------------------------------------
4954

5055

51-
def _subprocess_init(user_apps: list[str]) -> None:
56+
def _start_parent_watchdog(
57+
parent_pid: int, interval_seconds: float = WATCHDOG_INTERVAL_SECONDS
58+
) -> None:
59+
"""Terminate this process if the parent process exits or PPID changes.
60+
61+
This runs in a background daemon thread so it never blocks pool work.
62+
"""
63+
64+
def _watch() -> None:
65+
while True:
66+
# If PPID changed (parent died and we were reparented), exit.
67+
if os.getppid() != parent_pid:
68+
os._exit(1)
69+
70+
# Best-effort liveness probe in case PPID was reused.
71+
try:
72+
os.kill(parent_pid, 0)
73+
except OSError:
74+
os._exit(1)
75+
76+
time.sleep(interval_seconds)
77+
78+
threading.Thread(target=_watch, name="parent-watchdog", daemon=True).start()
79+
80+
81+
def _subprocess_init(user_apps: list[str], parent_pid: int) -> None:
82+
_start_parent_watchdog(parent_pid)
5283
for app_target in user_apps:
5384
load_user_app(app_target)
5485

0 commit comments

Comments
 (0)