Repository navigation
asyncio.subprocess.Process race condition kills an unrelated process on Unix #127049
Copy link
Copy link
Closed
Labels
3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes3.16new features, bugs and security fixesnew features, bugs and security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Nov 20, 2024 Thanks for the report!
I can reproduce the error locally on my linux boxReacted by Ganden Schaffner- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
on Nov 21, 2024 I think the correct way to fix this is to make the reaping of the child process and the publication of the return code atomic with respect to the event loop so that there is no inherent race of calling kill or signaling to wrong process.
The ThreadedChildWatcher can use
os.waitidinstead ofos.waitpidso that the PID is not reused and the event loop reaps the child process withos.waitpidand sets the return code. This would ensure that there is no window of time where the child process has been reaped but the event loop thread does not has the return code set.Reacted by Ganden Schaffner- added 5 commits that reference this issue
on Jul 16, 2026 I created #153810 to implement this.
- added3.16new features, bugs and security fixesnew features, bugs and security fixes
on Jul 16, 2026 Running the reproducer on main branch vs fix branch:
- main branch
_watcher_case = '_ThreadedChildWatcher' --DBG: start waitpid(55300, 0) @ process.pid = 55300 --DBG: kill(55300, 9) File "/Users/kumaraditya/work/cpython/Lib/threading.py", line 1160, in run self._target(*self._args, **self._kwargs) File "/Users/kumaraditya/work/cpython/Lib/asyncio/unix_events.py", line 934, in _do_waitpid pid, status = os.waitpid(expected_pid, 0) --DBG: finish waitpid(55300, 0) = (55300, 9) --DBG: kill(55300, 9) Traceback (most recent call last): File "/Users/kumaraditya/work/cpython/main.py", line 138, in <module> asyncio.run(main()) ~~~~~~~~~~~^^^^^^^^ File "/Users/kumaraditya/work/cpython/Lib/asyncio/runners.py", line 205, in run return runner.run(main) ~~~~~~~~~~^^^^^^ File "/Users/kumaraditya/work/cpython/Lib/asyncio/runners.py", line 128, in run return self._loop.run_until_complete(task) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^ File "/Users/kumaraditya/work/cpython/Lib/asyncio/base_events.py", line 725, in run_until_complete return future.result() ~~~~~~~~~~~~~^^ File "/Users/kumaraditya/work/cpython/main.py", line 127, in main process.send_signal(Signal.SIGKILL) ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^ File "/Users/kumaraditya/work/cpython/Lib/asyncio/subprocess.py", line 140, in send_signal self._transport.send_signal(signal) ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^ File "/Users/kumaraditya/work/cpython/Lib/asyncio/base_subprocess.py", line 170, in send_signal os.kill(self._proc.pid, signal) ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/kumaraditya/work/cpython/main.py", line 43, in kill raise ValueError( "caller is trying to signal an already-freed PID! did a site call waitpid without telling the sites with references to that PID about it?" ) ValueError: caller is trying to signal an already-freed PID! did a site call waitpid without telling the sites with references to that PID about it?
- fix branch
_watcher_case = '_ThreadedChildWatcher' process.pid = 55596 --DBG: kill(55596, 9) --DBG: kill(55596, 9) --DBG: start waitpid(55596, 0) @ File "/Users/kumaraditya/work/cpython/Lib/asyncio/unix_events.py", line 972, in _reap_and_notify pid, returncode = self._reap(loop, expected_pid) File "/Users/kumaraditya/work/cpython/Lib/asyncio/unix_events.py", line 977, in _reap pid, status = os.waitpid(expected_pid, 0) --DBG: finish waitpid(55596, 0) = (55596, 9)
- added 3 commits that reference this issue
on Jul 19, 2026 - added3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes
on Jul 20, 2026
Metadata
Metadata
Assignees
Labels
3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes3.16new features, bugs and security fixesnew features, bugs and security fixesstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
Projects
- StatusShow more project fieldsDone
Bug report
Bug description:
On non-Windows platforms, there appears to be a race condition around asyncio's PID lifetime management that results in
asyncio.subprocess.Process.[send_signal | terminate | kill]signaling the wrong process, by callingkillon an already-freed PID. I observed this behavior occurring in the wild and I suspect this race condition was the underlying cause.A reproducer that consistently hits the race condition on 3.13.0, 3.14.0a1, main:
This bisects to GH-121126. GH-121126 fixed GH-87744 (where asyncio (on
_ThreadedChildWatcheronly) andPopenracedwaitpidcalls) by removing thewaitpidfromProcess.send_signal. The problem with this is that nowProcess.send_signalcallskillon a PID that may have already been freed by asyncio (on either_PidfdChildWatcheror_ThreadedChildWatcher). GH-82811 is analogous; IIUC GH-121126 inadvertently regressed GH-82811 except for processes spawned viaasynciorather than those spawned viasubprocess.CPython versions tested on:
3.13, 3.14, CPython main branch
Operating systems tested on:
Linux
Linked PRs
asyncio.subprocess.Processrace condition killing an unrelated process on Unix #127051