Skip to content

Fix SIGSEGV in Python stub on non-graceful (parent death) termination#446

Open
mrpre wants to merge 1 commit into
triton-inference-server:mainfrom
mrpre:main
Open

Fix SIGSEGV in Python stub on non-graceful (parent death) termination#446
mrpre wants to merge 1 commit into
triton-inference-server:mainfrom
mrpre:main

Conversation

@mrpre

@mrpre mrpre commented Jul 11, 2026

Copy link
Copy Markdown

Problem

Python backend stub processes segfault when the parent tritonserver
process goes away abruptly (SIGKILL after the K8s grace period, OOM-kill, or
a crash). In production this appears as bursts of identical crashes across
many stubs of the same model:

  triton_python_b[...]: segfault at ...210 ip ... error 6
      in libpthread-2.31.so[...]

error 6 = user-mode write to an unmapped page; the fault address is the
in-shm bi::interprocess_mutex.

Root cause

The stub's health-monitoring background thread (pb_stub.cc, main()) polls
ParentProcessActive(parent_pid). When the parent is gone it took the
"Non-graceful termination detected" branch, which called
Stub::DestroyInstance() and exit(1). Both unmap the shared-memory region
(~SharedMemoryManager -> ~mapped_region -> munmap, and again via the static
destructors run by exit()), while the RunCommand loop and the model
instance threads are still using that region and its process-shared
mutexes/semaphores. Unmapping it under them is a data race that faults them
with SIGSEGV (SEGV_MAPERR) inside pthread_mutex_*.

It is intermittent because it only crashes when another thread happens to be
in the userspace lock/unlock of the shm mutex during the window between the
munmap and the process actually exiting — which matches the batchy,
intermittent nature seen in production.

Fix

On non-graceful termination the background thread cannot safely destroy the
region (it cannot join the main thread, which is almost always blocked on or
holding a shm lock). Since the process is exiting and the parent owns and
recreates the region, terminate immediately with _exit(1) and run no
destructors.

When the parent (tritonserver) process dies abruptly -- SIGKILL after the
Kubernetes termination grace period, an OOM-kill, or a crash -- it cannot
send a Finalize command, so the stub's health-monitoring background thread
takes the "Non-graceful termination detected" path. That path called
Stub::DestroyInstance() and exit(1). Both unmap the shared-memory region
(DestroyInstance via ~SharedMemoryManager -> ~mapped_region -> munmap, and
exit() via static destructors) while the RunCommand loop and the model
instance threads are still operating on that region and its process-shared
mutexes.

Unmapping the region out from under those still-running threads is a data
race: a thread that touches a shm mutex after the region is unmapped faults
with SIGSEGV inside libpthread (SEGV_MAPERR on the lock word). Because a
whole pod's stubs share one parent, they all take this path at once and a
batch of them crash simultaneously at the same instruction.

The background thread cannot safely tear the region down: it cannot join the
main thread (which in turn joins it), and the main thread is almost always
either blocked on, or actively locking, a shm object. Since the process is
exiting anyway and the region is owned and reclaimed by the parent (the
kernel reclaims all mappings on process exit), terminate immediately with
_exit() so that no destructors run.

Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant