Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

224 lines
6.9 KiB
Python

from __future__ import annotations
import logging
import os
import signal
import sys
import threading
import time
from contextlib import contextmanager
from multiprocessing import Process
from typing import Callable, List, Optional
import psutil
from sglang.srt.utils.cudacore_pyspy_dump_utils import pyspy_dump_schedulers
logger = logging.getLogger(__name__)
class Watchdog:
@staticmethod
def create(
debug_name: str,
watchdog_timeout: Optional[float],
soft: bool = False,
test_stuck_time: float = 0,
) -> Watchdog:
if watchdog_timeout is None:
assert (
test_stuck_time == 0
), f"stuck tester can be enabled only if soft watchdog is enabled."
return _WatchdogNoop()
return _WatchdogReal(
debug_name=debug_name,
watchdog_timeout=watchdog_timeout,
soft=soft,
test_stuck_time=test_stuck_time,
)
def feed(self):
pass
@contextmanager
def disable(self):
yield
class _WatchdogReal(Watchdog):
def __init__(
self,
debug_name: str,
watchdog_timeout: float,
soft: bool = False,
test_stuck_time: float = 0,
):
self._counter = 0
self._active = True
self._test_stuck_time = test_stuck_time
self._test_stuck_triggered = False
self._raw = WatchdogRaw(
debug_name=debug_name,
get_counter=lambda: self._counter,
is_active=lambda: self._active,
watchdog_timeout=watchdog_timeout,
soft=soft,
)
logger.info(f"Watchdog {self._raw.debug_name} initialized.")
if self._test_stuck_time > 0:
logger.info(
f"Watchdog {self._raw.debug_name} is configured to use {test_stuck_time=}."
)
def feed(self):
# Only trigger the test stuck behavior once to avoid blocking server
# startup health checks while still testing watchdog timeout detection
if self._test_stuck_time > 0 and not self._test_stuck_triggered:
self._test_stuck_triggered = True
logger.info(
f"Watchdog {self._raw.debug_name} start deliberately stuck for {self._test_stuck_time}s"
)
time.sleep(self._test_stuck_time)
logger.info(
f"Watchdog {self._raw.debug_name} end deliberately stuck for {self._test_stuck_time}s"
)
self._counter += 1
@contextmanager
def disable(self):
assert self._active
self._active = False
try:
yield
finally:
assert not self._active
self._active = True
class _WatchdogNoop(Watchdog):
pass
class WatchdogRaw:
def __init__(
self,
debug_name: str,
get_counter: Callable[[], int],
is_active: Callable[[], bool],
watchdog_timeout: float,
soft: bool = False,
dump_info: Optional[Callable[[], str]] = None,
):
self.debug_name = debug_name
self.get_counter = get_counter
self.is_active = is_active
self.watchdog_timeout = watchdog_timeout
self.soft = soft
self.dump_info = dump_info
self.parent_process = psutil.Process().parent()
t = threading.Thread(target=self._watchdog_thread, daemon=True)
t.start()
def _watchdog_thread(self):
try:
while True:
self._watchdog_once()
except Exception as e:
logger.error(
f"{self.debug_name} watchdog thread crashed: {e}", exc_info=True
)
def _watchdog_once(self):
watchdog_last_counter = 0
watchdog_last_time = time.perf_counter()
while True:
current = time.perf_counter()
if self.is_active():
current_counter = self.get_counter()
if watchdog_last_counter == current_counter:
if current > watchdog_last_time + self.watchdog_timeout:
break
else:
watchdog_last_counter = current_counter
watchdog_last_time = current
time.sleep(self.watchdog_timeout / 2)
if self.dump_info is not None and (info_msg := self.dump_info()):
logger.error(f"{self.debug_name} debug info:\n{info_msg}")
pyspy_dump_schedulers()
logger.error(
f"{self.debug_name} watchdog timeout "
f"({self.watchdog_timeout=}, {self.soft=})"
)
print(file=sys.stderr, flush=True)
print(file=sys.stdout, flush=True)
if not self.soft:
# Wait for some time so that the parent process can print the error.
time.sleep(5)
self.parent_process.send_signal(signal.SIGQUIT)
class SubprocessWatchdog:
"""Monitors subprocess liveness and triggers SIGQUIT when a crash is detected.
When a subprocess crashes (e.g., NCCL timeout causing C++ std::terminate()),
Python exception handlers never run, leaving the main process as a zombie
service. This watchdog polls subprocess liveness in a daemon thread and
sends SIGQUIT to trigger proper cleanup.
See: https://github.com/sgl-project/sglang/issues/18421
"""
def __init__(
self,
processes: List[Process],
process_names: Optional[List[str]] = None,
interval: float = 1.0,
):
self._processes = processes
self._names = process_names or [f"process_{i}" for i in range(len(processes))]
self._interval = interval
self._stop_event = threading.Event()
self._thread: Optional[threading.Thread] = None
def start(self) -> None:
if self._thread is not None or not self._processes:
return
self._thread = threading.Thread(
target=self._monitor_loop, daemon=True, name="subprocess-watchdog"
)
self._thread.start()
def stop(self) -> None:
self._stop_event.set()
if self._thread is not None:
self._thread.join(timeout=self._interval * 2)
self._thread = None
def _monitor_loop(self) -> None:
try:
while not self._stop_event.wait(self._interval):
if self._check_processes():
return
except Exception as e:
logger.error(f"SubprocessWatchdog thread crashed: {e}", exc_info=True)
def _check_processes(self) -> bool:
for proc, name in zip(self._processes, self._names):
if proc.is_alive() or proc.exitcode == 0:
continue
logger.error(
f"Subprocess {name} (pid={proc.pid}) crashed "
f"with exit code {proc.exitcode}. "
f"Triggering SIGQUIT for cleanup..."
)
os.kill(os.getpid(), signal.SIGQUIT)
return True
return False