[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:
co-authored by
Claude Opus 4.8
parent
db061e97c0
commit
5f913c1135
@@ -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()
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user