[misc] Remove break-graph debug log; reclaim pid-less /dev/shm leaks in CI (#33929)

This commit is contained in:
Liangsheng Yin
2026-08-06 21:18:01 -07:00
committed by GitHub
parent 0c3a76fa0a
commit afa79330b8
3 changed files with 51 additions and 22 deletions
@@ -22,7 +22,6 @@ tensors remain valid across replays — we don't need Python-managed bridge
buffers to keep break-point tensors at stable addresses. buffers to keep break-point tensors at stable addresses.
""" """
import logging
import threading import threading
from contextvars import ContextVar from contextvars import ContextVar
from typing import Any, Callable, Optional from typing import Any, Callable, Optional
@@ -39,8 +38,6 @@ from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph.cuda_ut
) )
from sglang.srt.utils import get_device_module, is_hip, is_xpu from sglang.srt.utils import get_device_module, is_hip, is_xpu
logger = logging.getLogger(__name__)
_is_xpu = is_xpu() _is_xpu = is_xpu()
__all__ = [ __all__ = [
@@ -226,8 +223,6 @@ def eager_on_graph(enable: bool, capture_stub: Optional[Callable] = None):
if capture is None: if capture is None:
return inner(*args, **kwargs) return inner(*args, **kwargs)
logger.debug("Break graph due to function: %s", inner.__name__)
# End the segment that captured up to this break point. # End the segment that captured up to this break point.
capture._end_current_segment() capture._end_current_segment()
+21 -14
View File
@@ -4,10 +4,10 @@ SGLang processes are torn down with SIGKILL (kill_process_tree, PDEATHSIG),
which skips every Python-level unlink path, so /dev/shm segments accumulate which skips every Python-level unlink path, so /dev/shm segments accumulate
until the tmpfs is full and the next scheduler init dies with SIGBUS. until the tmpfs is full and the next scheduler init dies with SIGBUS.
Segments created through make_shm_name() embed the creator pid, which lets a Pid-stamped names (see _creator_pid) are unlinked once their creator is dead;
later server startup safely unlink segments whose creator is gone. The sweep pid-less families (_ORPHAN_PREFIXES) are unlinked unconditionally, safe only
only runs in CI (single-tenant runner containers); on shared dev machines a because the sweep runs at CI job start right after killall.py. CI-only
pid check against another user's process is not authoritative, so we skip. (SGLANG_IS_IN_CI): both rules assume a single-tenant runner container.
""" """
import logging import logging
@@ -20,10 +20,16 @@ logger = logging.getLogger(__name__)
_SHM_DIR = Path("/dev/shm") _SHM_DIR = Path("/dev/shm")
_SGL_SHM_PREFIX = "sgl_shm" _SGL_SHM_PREFIX = "sgl_shm"
_ORPHAN_PREFIXES = (
"sglang_loads_", # managers/load_snapshot.py slot files
"cuda.shm.", # CUDA IPC segments
"nccl-", # NCCL communicator segments
"sem.loky-", # loky/joblib semaphores
)
def make_shm_name(kind: str) -> str: def make_shm_name(kind: str) -> str:
"""Name a shared-memory segment so cleanup_stale_shm can identify and """Pid-stamped name (sgl_shm_<kind>_<pid>_<rand>) the sweep can reclaim."""
reclaim it after its creator process dies: sgl_shm_<kind>_<pid>_<rand>."""
return f"{_SGL_SHM_PREFIX}_{kind}_{os.getpid()}_{uuid.uuid4().hex[:8]}" return f"{_SGL_SHM_PREFIX}_{kind}_{os.getpid()}_{uuid.uuid4().hex[:8]}"
@@ -60,11 +66,10 @@ def _pid_alive(pid: int) -> bool:
def cleanup_stale_shm() -> None: def cleanup_stale_shm() -> None:
"""Unlink shared-memory segments whose creator process is dead. """Unlink leaked shared-memory segments (rules in module docstring).
CI-only: gated on SGLANG_IS_IN_CI because the pid-liveness check is only Best-effort: never raises, since a failed sweep must not block server
trustworthy when the container runs one job at a time. Best-effort: never startup.
raises, since a failed sweep must not block server startup.
""" """
try: try:
_cleanup_stale_shm_impl() _cleanup_stale_shm_impl()
@@ -75,9 +80,8 @@ def cleanup_stale_shm() -> None:
def _is_in_ci() -> bool: def _is_in_ci() -> bool:
# Read the env var directly (same semantics as sglang.utils.is_in_ci) so # Same semantics as sglang.utils.is_in_ci, read directly so the module
# this module stays import-free and runnable by path from CI scripts # stays import-free (CI runs it by path before sglang is installed).
# before sglang is installed.
return os.environ.get("SGLANG_IS_IN_CI", "false").lower() in ("true", "1") return os.environ.get("SGLANG_IS_IN_CI", "false").lower() in ("true", "1")
@@ -96,10 +100,13 @@ def _cleanup_stale_shm_impl() -> None:
return return
for entry in entries: for entry in entries:
pid = _creator_pid(entry.name) pid = _creator_pid(entry.name)
if pid is None or pid == os.getpid() or _pid_alive(pid): if pid is not None:
# A recycled pid reads as alive, so pid-reuse degrades to # A recycled pid reads as alive, so pid-reuse degrades to
# under-collection (segment leaks), never to deleting a live # under-collection (segment leaks), never to deleting a live
# segment. Keep that bias when changing this check. # segment. Keep that bias when changing this check.
if pid == os.getpid() or _pid_alive(pid):
continue
elif not entry.name.startswith(_ORPHAN_PREFIXES):
continue continue
try: try:
size = entry.stat().st_size size = entry.stat().st_size
@@ -44,13 +44,13 @@ class TestCleanupStaleShm(unittest.TestCase):
def _make_segment(self, name: str) -> str: def _make_segment(self, name: str) -> str:
shm = shared_memory.SharedMemory(create=True, size=4096, name=name) shm = shared_memory.SharedMemory(create=True, size=4096, name=name)
shm.close() shm.close()
self.addCleanup(self._unlink_quiet, name) self.addCleanup(self._unlink_quiet, f"/dev/shm/{name}")
return name return name
@staticmethod @staticmethod
def _unlink_quiet(name: str): def _unlink_quiet(path: str):
try: try:
shared_memory.SharedMemory(name=name).unlink() os.unlink(path)
except FileNotFoundError: except FileNotFoundError:
pass pass
@@ -121,6 +121,33 @@ class TestCleanupStaleShm(unittest.TestCase):
self.assertFalse(os.path.exists(f"/dev/shm/{stale}")) self.assertFalse(os.path.exists(f"/dev/shm/{stale}"))
def _make_raw_file(self, name: str) -> str:
"""Orphan families are plain files, not shared_memory segments."""
path = f"/dev/shm/{name}"
with open(path, "wb") as f:
f.write(b"\0" * 4096)
self.addCleanup(self._unlink_quiet, path)
return path
@unittest.skipUnless(
os.environ.get("SGLANG_IS_IN_CI", "").lower() in ("true", "1"),
"sweeps orphan families unconditionally; only safe on a CI runner",
)
def test_orphan_family_sweep(self):
stale = [
self._make_raw_file("sglang_loads_test_deadbeef.shm"),
self._make_raw_file("cuda.shm.0.deadbeef.1"),
self._make_raw_file("nccl-testonly"),
self._make_raw_file("sem.loky-0-testonly"),
]
unknown = self._make_raw_file("unknown_family_file")
cleanup_stale_shm()
for path in stale:
self.assertFalse(os.path.exists(path), path)
self.assertTrue(os.path.exists(unknown))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()