[Fix] Emulate PDEATHSIG on macOS to prevent orphaned worker processes (#27190)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lijuan Tang
2026-06-10 17:43:27 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent db061e97c0
commit 5f913c1135
2 changed files with 71 additions and 1 deletions
@@ -0,0 +1,60 @@
"""Parent-death watchdog for MLX workers on Apple Silicon.
macOS has no ``PR_SET_PDEATHSIG`` equivalent, so the kernel will not signal a
worker process when its parent dies; the worker would be reparented to PID 1
and leak (holding GPU/host memory and ports). This module emulates PDEATHSIG
with a daemon thread that watches the parent PID via kqueue and SIGKILLs the
current process once it gets orphaned.
"""
import os
import select
import signal
import threading
def start_parent_death_watcher() -> None:
"""SIGKILL this process once its current parent exits (macOS only).
kqueue with an ``EVFILT_PROC`` / ``NOTE_EXIT`` filter is the native,
event-driven mechanism on macOS (exposed via ``select.kqueue`` /
``select.kevent``), so the watcher thread blocks until the parent actually
exits instead of waking up to poll.
``SIGKILL`` is sent from this watcher thread and is uncatchable /
unblockable, so it works even when the main thread is stuck inside a
blocking native call (e.g. an MLX/Metal ``mx.eval`` / ``.tolist()``).
"""
original_ppid = os.getppid()
def _watch_parent():
kq = select.kqueue()
kev = select.kevent(
original_ppid,
filter=select.KQ_FILTER_PROC,
flags=select.KQ_EV_ADD,
fflags=select.KQ_NOTE_EXIT,
)
try:
# Register the EVFILT_PROC / NOTE_EXIT watch on the parent PID.
kq.control([kev], 0, None)
except (ProcessLookupError, OSError):
# The parent already exited before we could register the watch
# (ESRCH); we are already orphaned.
os.kill(os.getpid(), signal.SIGKILL)
return
# Guard against the race where the parent exits between reading
# original_ppid and registering the watch above.
if os.getppid() != original_ppid:
os.kill(os.getpid(), signal.SIGKILL)
return
# Block until the parent exits, then terminate ourselves.
kq.control(None, 1, None)
os.kill(os.getpid(), signal.SIGKILL)
watcher = threading.Thread(
target=_watch_parent,
name="parent-death-watcher",
daemon=True,
)
watcher.start()
+11 -1
View File
@@ -2523,8 +2523,18 @@ def kill_itself_when_parent_died():
PR_SET_PDEATHSIG = 1
libc = ctypes.CDLL("libc.so.6")
libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
elif sys.platform == "darwin":
# macOS has no PR_SET_PDEATHSIG equivalent; the MLX backend provides a
# kqueue-based watchdog that SIGKILLs this worker once it is orphaned.
from sglang.srt.hardware_backend.mlx.parent_watchdog import (
start_parent_death_watcher,
)
start_parent_death_watcher()
else:
logger.warning("kill_itself_when_parent_died is only supported in linux.")
logger.warning(
"kill_itself_when_parent_died is only supported on linux and macOS."
)
class UvicornAccessLogFilter(logging.Filter):