Skip to content

[Bugfix] Keep force-killing the process tree when a child exits mid-walk - #59984

Merged
njhill merged 4 commits into
vllm-project:mainfrom
liulanze:fix/kill-process-tree-nosuchprocess
Oct 14, 2026
Merged

njhill merged 4 commits into
vllm-project:mainfrom
liulanze:fix/kill-process-tree-nosuchprocess

Conversation

@liulanze

@liulanze liulanze commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Fix a regression from #52314: kill_process_tree stops early, leaving the rest of the tree and the target alive, when a child exits between parent.children() and child.name().

Claims

  • kill_process_tree now SIGKILLs every remaining descendant and the target pid even if a child is reaped while it walks the tree.
  • Before this change, psutil.NoSuchProcess escaped the function. Callers such as vllm/v1/utils.py::shutdown() then abandoned their loop over the remaining processes, so EngineCores, API servers or workers that were meant to be force-killed survived.

Validation

The new test tests/utils_/test_system_utils.py::test_kill_process_tree_skips_child_that_exits_mid_walk:

  • starts four real processes and makes children() report three of them as the target's children;
  • ends one of the three with SIGTERM and reaps it before kill_process_tree runs, so psutil's own name() raises NoSuchProcess for it;
  • asserts that this child exited from that SIGTERM, and that the other two children and the target exit with SIGKILL.

Results on Linux (base 04462732a4, Python 3.12):

psutil main this PR
6.1.0 (pinned in vLLM's CUDA/CPU test requirements) fails: NoSuchProcess: process no longer exists, from child.name() passes
7.2.2 (latest; pinned in vLLM's ROCm/XPU test requirements) fails: same passes
  • pytest tests/utils_/test_system_utils.py: 5 passed with each psutil version. The new test also passed 30 runs in a row with each.
  • pre-commit run --hook-stage manual (the stage CI runs) passes on the changed files, including ruff, typos and mypy.

Details

Root cause. #52314 added logger.warning(..., child.name(), child.pid) inside the existing contextlib.suppress(ProcessLookupError). On Linux, psutil.Process.name() re-reads /proc/<pid>/stat on every call; only Windows caches it. Once the child has been reaped it raises NoSuchProcess, which is not a ProcessLookupError. Before #52314 the loop only read child.pid, which only returns the stored pid, so this path could not raise.

Why the window is real. The force-kill path runs when a process did not exit within its grace period. Typically that process is still tearing down and reaping its own children, for example EngineCore joining its workers after SIGTERM, so a child listed by children() can be gone by the time name() runs.

Fix. Also suppress NoSuchProcess for that child. The child is already gone, so it needs no SIGKILL, and skipping it avoids signalling a pid that may have been recycled. This matches the existing psutil.NoSuchProcess guard at the top of the same function and vllm/entrypoints/launchers/dp_supervisor.py.

Affected callers.

  • vllm/v1/utils.py::shutdown(), used by CoreEngineProcManager, APIServerProcessManager and the DP coordinator.
  • vllm/v1/utils.py::_shutdown_subprocesses(), used by RustFrontendProcessManager.
  • dp_supervisor.py already suppresses the exception, but before this fix it silently skipped killing the rest of the tree.

Not a duplicate. I found no issue or PR that reports or fixes this. I searched kill_process_tree, NoSuchProcess and "force killing sub process" for anything after #52314 was merged on 2026-09-30.

AI assistance (Claude Code) was used to find this, write the patch and the test, and draft this description. I reviewed every changed line and ran the tests above.

Co-authored-by: Claude Opus 5.5 noreply@anthropic.com


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).

  • AI assistance was used during the creation of this PR.

  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.

  • Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.

  • Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.

  • Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added the bug Something isn't working label Oct 4, 2026

@chessing234 chessing234 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

everything seems fine, ready to merge.

@andyxning andyxning self-assigned this Oct 7, 2026
@mergify

mergify Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @liulanze.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

vllm-project#52314 started logging each child's name() before SIGKILLing it, inside
suppress(ProcessLookupError). psutil raises NoSuchProcess, not
ProcessLookupError, when the child was reaped after children() listed
it. That race is likely on the force-kill path, where the target is
still tearing down and reaping its own children. The exception escapes
kill_process_tree, so the remaining children and the target itself are
never killed, and callers such as shutdown() abort their loop over the
remaining processes.

Suppress NoSuchProcess as well: a child that is already gone needs no
SIGKILL, and skipping it avoids signalling a recycled pid.

Signed-off-by: Lanze Liu <lanzetech@gmail.com>
@liulanze
liulanze force-pushed the fix/kill-process-tree-nosuchprocess branch from 9b42062 to e52d119 Compare October 9, 2026 19:39
@mergify mergify Bot removed the needs-rebase label Oct 9, 2026
@liulanze

liulanze commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto the latest main to resolve the conflict with #59336. The conflict was only in the test file, where both PRs added imports and a test; the fix itself is unchanged.

@andyxning this is a small follow-up to #52314: the new child.name() log call can raise psutil.NoSuchProcess when a child exits mid-walk, which stops the rest of the force-kill. Could you take a look when you have a moment? Thanks!

Comment thread tests/utils_/test_system_utils.py Resolved
Comment thread vllm/utils/system_utils.py Resolved
psutil's children() looks the target up again (Process(pid).create_time())
and raises NoSuchProcess if it has exited and been reaped since the first
lookup. Treat that like the existing lookup failure and return.

The tests now use real processes, so it is psutil's own name() and
children() that raise NoSuchProcess.

Signed-off-by: Lanze Liu <lanzetech@gmail.com>
@liulanze
liulanze requested a review from andyxning October 10, 2026 21:43
liulanze and others added 2 commits October 12, 2026 16:08
Per review, keep this PR to the vllm-project#52314 regression: move children() back
out of the try and drop the target-exit test.

In the remaining test, end the child that exits mid-walk with SIGTERM
instead of SIGKILL and assert it, so it is clearly told apart from the
processes that kill_process_tree SIGKILLs.

Signed-off-by: Lanze Liu <lanzetech@gmail.com>
@andyxning

Copy link
Copy Markdown
Member

/lgtm

@andyxning

Copy link
Copy Markdown
Member

/ci run

@andyxning andyxning added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 13, 2026
@github-actions

Copy link
Copy Markdown

✅ @liulanze, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Your branch must contain every commit currently on its upstream target branch. Merge or rebase onto the latest target branch, then rerun the command. Append --allow-stale to a run command to test an outdated branch at your own risk.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #94375 for commit ea977b27445f.

@vllm-agent

Copy link
Copy Markdown
Collaborator

CI selector (shadow): 90 test steps (130 jobs) instead of 57 (73 jobs)

Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works.

Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.

steps (jobs) Today's rules Selector Would skip Would add
NVIDIA, CPU and others 57 (73) 90 (130) 28 (36) 61 (93)
AMD mirrors 48 (59) 14 (20) 44 (52) 10 (13)
Selector would run (90)
  • async-engine-inputs-utils-worker
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-tests-extra-initialization ×14
  • basic-models-tests-other
  • batch-invariance-b200
  • batch-invariance-h100
  • benchmarks-cli-test
  • cpu-multi-modal-model-tests-n ×4
  • crosslayer-kv-layout-distributed-nixlconnector-pd-accuracy-tests-4-gpus
  • distributed-compile-rpc-tests-2-gpus
  • distributed-dp-tests-2-gpus
  • distributed-dp-tests-4-gpus
  • distributed-flashinfer-nixlconnector-pd-accuracy-4-gpus
  • distributed-model-tests-2-gpus ×3
  • distributed-mooncakeconnector-pd-accuracy-4-gpus
  • distributed-nixlconnector-pd-accuracy-4-gpus
  • distributed-torchrun-examples-4-gpus
  • dp-ep-distributed-nixlconnector-pd-accuracy-tests-4-gpus
  • e2e-scheduling-accuracy-1-gpu
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • entrypoints-unit-tests
  • examples
  • fault-tolerance-e2e-2xh100
  • fusion-e2e-quick-h100
  • fusion-e2e-tp2-ar-rms-config-sweep-h100
  • fusion-e2e-tp2-b200
  • fusion-e2e-tp2-quick-h100
  • hybrid-ssm-nixlconnector-pd-accuracy-tests-4-gpus
  • hybrid-ssm-nixlconnector-pd-prefix-cache-2-gpus
  • kimi-k3-prefix-cache-4xb200 ×2
  • kv-offload-large
  • kv-offload-medium
  • kv-offload-small
  • language-models-tests-extra-standard ×2
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • lm-eval-dspark-watermark-2xh100
  • lm-eval-small-models
  • lm-eval-turboquant-k3v4nc
  • lm-eval-turboquant-k8v4
  • lm-eval-turboquant-t3nc
  • lm-eval-turboquant-t4nc
  • lm-eval-watermarking
  • lora ×4
  • lora-tp-distributed ×4
  • model-executor
  • mooncake-ec-tcp-e2e-2-gpus
  • mrcr-eval-small-models
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-4-other-whisper
  • multiconnector-nixl-offloading-pd-accuracy-2-gpus
  • multiconnector-nixl-offloading-pd-edge-cases-2-gpus
  • nixlconnector-pd-edge-cases-2-gpus
  • openai-api-correctness
  • pipeline-context-parallelism-4-gpus
  • plugin-tests-2-gpus
  • push-nixlconnector-pp-prefill-pd-accuracy-4-gpus
  • pytorch-compilation-unit-tests
  • pytorch-fullgraph-test
  • quantization ×4
  • quantized-moe-test-b200
  • rayexecutorv2-4-gpus
  • replayssm-e2e
  • rust-frontend-core-correctness
  • rust-frontend-distributed
  • rust-frontend-openai-coverage
  • rust-frontend-serve-admin-coverage
  • rust-frontend-tool-use
  • samplers-test
  • scale-out-ec-e2e-2-gpus
  • spec-decode-draft-model ×4
  • spec-decode-eagle-2-llama3-qwen-vl-other
  • v1-core
  • v1-kv-connectors ×4
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-sample
  • v1-spec-decode
Would skip (today's rules run them) (28)
  • ascend-npu-test
  • basic-models-test-other-cpu
  • basic-models-tests-initialization
  • cpu-language-generation-and-pooling-model-tests ×3
  • cpu-multimodal-config
  • cpu-params-env-tokenizers-parser
  • cpu-reasoning-renderers
  • cpu-tool-parsers
  • engine
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • language-models-tests-granite-l4-compatibility
  • metrics-tracing-2-gpus
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-processor ×4
  • multi-modal-processor-cpu ×4
  • platform-tests
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • regression
  • rl-entrypoints-tests
  • v1-executor-worker
  • v1-kv-offload
  • v1-others-cpu
Would add (today's rules do not run them) (61)
  • basic-models-tests-extra-initialization ×14 (Python record)
  • batch-invariance-b200 (Python record)
  • batch-invariance-h100 (Python record)
  • cpu-multi-modal-model-tests-n ×4 (code map)
  • crosslayer-kv-layout-distributed-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • distributed-compile-rpc-tests-2-gpus (Python record)
  • distributed-dp-tests-2-gpus (Python record)
  • distributed-dp-tests-4-gpus (code map)
  • distributed-flashinfer-nixlconnector-pd-accuracy-4-gpus (Python record)
  • distributed-model-tests-2-gpus ×3 (code map)
  • distributed-mooncakeconnector-pd-accuracy-4-gpus (Python record)
  • distributed-nixlconnector-pd-accuracy-4-gpus (Python record)
  • distributed-torchrun-examples-4-gpus (Python record)
  • dp-ep-distributed-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • e2e-scheduling-accuracy-1-gpu (Python record)
  • entrypoints-unit-tests (Python record)
  • examples (Python record)
  • fault-tolerance-e2e-2xh100 (Python record)
  • fusion-e2e-quick-h100 (Python record)
  • fusion-e2e-tp2-ar-rms-config-sweep-h100 (Python record)
  • fusion-e2e-tp2-b200 (Python record)
  • fusion-e2e-tp2-quick-h100 (Python record)
  • hybrid-ssm-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • hybrid-ssm-nixlconnector-pd-prefix-cache-2-gpus (Python record)
  • kimi-k3-prefix-cache-4xb200 ×2 (Python record)
  • kv-offload-large (Python record)
  • kv-offload-medium (Python record)
  • kv-offload-small (Python record)
  • language-models-tests-extra-standard ×2 (Python record)
  • lm-eval-dspark-watermark-2xh100 (Python record)
  • lm-eval-small-models (Python record)
  • lm-eval-turboquant-k3v4nc (Python record)
  • lm-eval-turboquant-k8v4 (Python record)
  • lm-eval-turboquant-t3nc (Python record)
  • lm-eval-turboquant-t4nc (Python record)
  • lm-eval-watermarking (Python record)
  • lora ×4 (Python record)
  • lora-tp-distributed ×4 (Python record)
  • model-executor (Python record)
  • mooncake-ec-tcp-e2e-2-gpus (Python record)
  • mrcr-eval-small-models (Python record)
  • multiconnector-nixl-offloading-pd-accuracy-2-gpus (Python record)
  • multiconnector-nixl-offloading-pd-edge-cases-2-gpus (Python record)
  • nixlconnector-pd-edge-cases-2-gpus (Python record)
  • openai-api-correctness (Python record)
  • pipeline-context-parallelism-4-gpus (Python record)
  • plugin-tests-2-gpus (Python record)
  • push-nixlconnector-pp-prefill-pd-accuracy-4-gpus (Python record)
  • quantization ×4 (Python record)
  • quantized-moe-test-b200 (Python record)
  • rayexecutorv2-4-gpus (Python record)
  • replayssm-e2e (Python record)
  • rust-frontend-core-correctness (Python record)
  • rust-frontend-distributed (Python record)
  • rust-frontend-openai-coverage (Python record)
  • rust-frontend-serve-admin-coverage (Python record)
  • rust-frontend-tool-use (Python record)
  • samplers-test (Python record)
  • scale-out-ec-e2e-2-gpus (Python record)
  • spec-decode-draft-model ×4 (Python record)
  • spec-decode-eagle-2-llama3-qwen-vl-other (Python record)
AMD mirrors: would skip (44)
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-models-tests-initialization
  • basic-models-tests-other
  • benchmarks-cli-test
  • engine
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-multimodal
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • entrypoints-integration-speech_to_text
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • metrics-tracing-2-gpus
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-3-llava-qwen2-vl
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • platform-tests
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • regression
  • rl-entrypoints-tests
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-logits-oracle
  • v1-metrics-lmeval
  • v1-sample
  • v1-spec-decode
AMD mirrors: would add (10)
  • distributed-dp-tests-2-gpus (Python record)
  • distributed-dp-tests-4-gpus (code map)
  • distributed-torchrun-shutdown-tests-2-gpus (Python record)
  • dp-ep-distributed-nixlconnector-pd-accuracy-tests-4-gpus (Python record)
  • engine-1-gpu (Python record)
  • entrypoints-unit-tests (Python record)
  • extract-hidden-states-integration-2-gpus (Python record)
  • lora-tp-distributed ×4 (Python record)
  • rust-frontend-distributed (Python record)
  • rust-frontend-openai-coverage (Python record)

2 changed files · base c5422f4c46 · head ea977b2744 · Python record: build 93039 at 1e5d0ea888 · kernel record: table fe341a5 (build 94315), map fe341a5 · not counted: 11 build steps, 5 A100 steps the generator no longer emits, 64 optional steps the selector would also run

@andyxning

Copy link
Copy Markdown
Member

/cc @DarkLight1337 PTAL.

@njhill njhill left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @liulanze!

@njhill
njhill merged commit f4b8d43 into vllm-project:main Oct 14, 2026
141 checks passed
AndreasKaratzas pushed a commit to AndreasKaratzas/vllm that referenced this pull request Oct 16, 2026
…alk (vllm-project#59984)

Signed-off-by: Lanze Liu <lanzetech@gmail.com>
Co-authored-by: Ning Xie <andy.xning@gmail.com>

Signed-off-by: Andreas Karatzas <AndreasKaratzas@users.noreply.github.com>
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
…alk (vllm-project#59984)

Signed-off-by: Lanze Liu <lanzetech@gmail.com>
Co-authored-by: Ning Xie <andy.xning@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants