← 输入框|github.com|刷新
Skip to content

gh-160787: Reap the killed child in subprocess.run() on KeyboardInterrupt - #160941

Open
JakkulaVeerababu wants to merge 1 commit into
python:mainfrom
JakkulaVeerababu:fix-subprocess-run-reap-on-kill
Open

JakkulaVeerababu wants to merge 1 commit into
python:mainfrom
JakkulaVeerababu:fix-subprocess-run-reap-on-kill

Conversation

@JakkulaVeerababu

@JakkulaVeerababu JakkulaVeerababu commented Oct 30, 2026 •

Copy link
Copy Markdown

I found that subprocess.run() had a cleanup issue when Ctrl-C (KeyboardInterrupt) occurred. The child process was correctly killed, but it was not being reaped. run() relied on Popen.exit() for cleanup, but exit() intentionally skips waiting for the child during KeyboardInterrupt. As a result, Python could emit a misleading ResourceWarning saying the subprocess was still running even though it had already been terminated.

To fix this, I updated Lib/subprocess.py so that run() explicitly reaps the child process immediately after killing it. I used a bounded wait of up to 1 second to ensure the cleanup happens without violating CPython's requirement that Ctrl-C handling must not block indefinitely.

I also added a regression test, test_run_keyboardinterrupt_reaps_killed_child, in Lib/test/test_subprocess.py. The test simulates a KeyboardInterrupt during subprocess.run() and verifies that the child process has been collected by checking that it receives an exit status. The test fails before the fix and passes afterward.

Finally, I added a NEWS entry documenting the bug fix, while leaving the existing Ctrl-C tests unchanged to ensure no existing behavior was affected.

AI assistance

I used Claude Code (an AI assistant) to investigate and draft this change; I reviewed it and can explain it.

Root cause. On an exception, run() kills the child and relies on Popen.__exit__() to wait. After a KeyboardInterrupt, __exit__() skips the wait once the SIGINT grace period was used (_sigint_wait_secs == 0), so the killed child is never reaped and __del__() warns before it polls.

Fix. run() reaps the killed child with process._wait(timeout=1). A plain process.wait() would block indefinitely and break the bpo-25942 guarantee that test_run_keyboardinterrupt_no_kill checks ("no open-ended wait() after the first"). _wait() is used rather than wait() because wait() adds its own SIGINT grace period, which no longer applies to a child that was just killed. __exit__() uses _wait() the same way.

Tests. New test_run_keyboardinterrupt_reaps_killed_child: it fails without the change ("unexpectedly None") and passes with it. The existing *keyboardinterrupt* tests pass unchanged. A real-signal repro (timer sends SIGINT to the parent, start_new_session=True child) emitted the ResourceWarning 3/3 times before the fix and 0/5 after.

./python -m test test_subprocess -m "*keyboardinterrupt*"   # 4 tests OK
./python -m test test_subprocess -j4                         # 372 run, OK
make patchcheck                                              # NEWS entry present, clean

Tested on Linux (WSL2), CPython main at ef1fce0 (3.16.0a1+).

🤖 Generated with Claude Code

…dInterrupt

On an exception, run() kills the child and relied on Popen.__exit__() to
wait for it.  But after a KeyboardInterrupt, __exit__() skips the wait
once the SIGINT grace period has been used, so the killed child was
never reaped and Popen.__del__() emitted a ResourceWarning.

run() now waits for the killed child itself, with a timeout so that
^C never blocks indefinitely (bpo-25942).  It calls _wait() directly
like __exit__() does: the child was already killed, so wait()'s extra
SIGINT grace period does not apply.
@python-cla-bot

python-cla-bot Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-30T03:25:55.829850Z
We couldn’t complete this review. Comment @codex review to try again.
4160e0e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant