GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-158570: Fix ProcessPoolExecutor shutdown deadlock when the result pipe is full by tfzee · Pull Request #158571 · python/cpython

Conversation

@tfzee

@tfzee tfzee commented Oct 1, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Fixes a deadlock in ProcessPoolExecutor.shutdown().
When shutting down, each worker receiving the None sentinel wrote its PID to the result queue. While joining workers in _join_executor_internals(), the manager thread no longer reads that queue. If the result pipe is full, for example with many workers or a small pipe buffer, a worker will block writing its PID and never exits. p.join() then hangs.
This PR removes the shutdown-only PID write from _process_worker(). Since nothing consumes the PID.

The new test makes the hang deterministic.
Right before the workers are told to exit, a thread fills up the result queue.
This is to emulate the a lot of workers exiting and writing their results back.
Any worker that still writes to the result queue on exit would block behind this.

Prepared with help from an AI assistant (Claude). I reviewed and verified the change.

  • Issue: ProcessPoolExecutor.shutdown(wait=True) deadlocks when worker exit messages fill the result pipe #158570

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
tfzee added 2 commits October 1, 2026 13:30
@tfzee
Fix ProcessPoolExecutor shutdown deadlock when result pipe is full
7f12e9c
@tfzee
Add NEWS entry
6c0483c
@python-cla-bot

python-cla-bot Bot commented Oct 1, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot mentioned this pull request Oct 1, 2026
ProcessPoolExecutor.shutdown(wait=True) deadlocks when worker exit messages fill the result pipe #158570
Open
@bedevere-app bedevere-app Bot added the awaiting review label Oct 1, 2026
@tfzee
Fix NEWS entry reference
4ec18e8
ngoldbaum
ngoldbaum reviewed Oct 7, 2026
View reviewed changes

@ngoldbaum ngoldbaum left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I'm not very familiar with this code but I got pinged to review this. I used an AI model, it found an alternate fix. See below but please take it worth a grain of salt and try for yourself, along with looking at the 2023 change my comment links to.

@gpshead does have more recent context about this code and might have better suggestions.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

nascheme reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/concurrent/futures/process.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/test/test_concurrent_futures/test_deadlock.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

tfzee added 2 commits October 8, 2026 16:01
@tfzee
Rework to instead not put PID into pipe
180a067
@tfzee
Make test more consistent
8f1da0f
ngoldbaum
ngoldbaum reviewed Oct 9, 2026
View reviewed changes

@ngoldbaum ngoldbaum left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

@tfzee, thanks! Looks good to me besides one issue in your test. One last thing: can you trim down the PR description a bit and update it to reflect the current approach? Your PR description will be the commit message for the squashed merge commit.

I don't feel comfortable approving and merging this because I've never worked in multiprocessing and my review so far was heavily AI-assisted.

@vstinner any chance you can take a look at this? For context, you removed some other obsolete code for handling PIDs in multiprocessing back in 2023 in #109906. Maybe you'd feel a bit more comfortable hitting the merge button than me?

A Quansight client hit the deadlock fixed by this PR which is why I'm looking at this PR. I think it's not the same organization hitting this that also led to @tfzee to working on this, so it seems like this is being hit by two different organizations using Python in production. Apparently some new CPUs came out this week with lots and lots of cores so these rare timing bugs are suddenly getting hit with a high likelihood.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_concurrent_futures/test_deadlock.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@nascheme

nascheme commented Oct 9, 2026

Copy link
Copy Markdown
Member

Could we remove the shutdown-only PID notification instead of adding a drain loop?

I think that's the better solution and it looks correct to me. However, I'm not familiar with multiprocessing internals either.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@tfzee
Fix test issue
6f0cf04
Co-authored-by: ngoldbaum <[email protected]>"
@gpshead gpshead self-assigned this Oct 12, 2026
@ngoldbaum

ngoldbaum commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Gentle ping for review @gpshead

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nascheme

nascheme commented Nov 4, 2026

Copy link
Copy Markdown
Member

I got GPT Sol to help me review this.

The current patch looks ready to merge. Removing the shutdown-only PID write directly eliminates the deadlock: the manager has stopped draining results and is joining workers, so those writes can block forever. The integer-PID consumer was removed in #109906; worker recycling still uses _ResultItem.exit_pid and is unchanged.

We found no new regression mechanism for multithreaded callers, shutdown modes, or worker failure handling. The full test_concurrent_futures suite passed locally on Linux. Restoring just the deleted write made the new regression test fail as expected under fork, forkserver, and spawn. Additional tests with threaded callers, large results, cancellation, and worker recycling also passed.

One non-blocking test caveat: Windows skips the _wlock synchronization, so deterministic reproduction is not guaranteed there.

I also checked whether removing the worker's exit-time result_queue.put(os.getpid()) could break third-party code on 3.14 or 3.15.

I searched the sources and upstream test repos of 33 popular packages (the snake_pit set: numpy, pandas, cython, pytest, django, sqlalchemy, uvloop, lxml, sympy, sphinx, etc.) for ProcessPoolExecutor, concurrent.futures.process, _ResultItem, _ExecutorManagerThread, _process_worker and result_queue. Users found:

  • cython: Cythonize.py, Dependencies.py (with initializer=), runtests.py (max_tasks_per_child on GraalPy)
  • numpy: test_multiprocessing.py (spawn context, submit)
  • uvloop: tests/test_executors.py (run_in_executor)
  • pytest: testing/test_reports.py (exception chaining via submit().result())
  • lxml: download_artefacts.py (helper script)

All of these use only the public API. None of them reads _result_queue, subclasses the manager thread or depends on what goes through the result pipe.

Behaviour analysis: On 3.14, 3.15 and main, the only place the exit sentinel (None) is sent to workers is shutdown_workers(). That runs from join_executor_internals(), after the manager loop has stopped reading result_queue. So the PID written on exit was never consumed. If the manager loop had ever received one, process_result_item() would have failed on result_item.work_id. The "wake up queue management thread" comment dates from older versions that had an isinstance(result_item, int) branch, which no longer exists. During shutdown, worker exit is observed via p.join() and the process sentinels. The max_tasks_per_child exit path (exit_pid in _ResultItem) is unchanged.

The only behaviour change is that a worker no longer writes to the result pipe on its way out, so it can no longer block there. That is the deadlock this PR fixes.

Backporting: The process.py change applies cleanly to 3.14 and 3.15. The test applies cleanly to 3.15 and needs a minor manual merge on 3.14.

Conclusion: this seems to have a low risk of breaking existing code.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

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

Reviewers

@ngoldbaum ngoldbaum ngoldbaum left review comments

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@gpshead gpshead

Labels

awaiting review

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

4 participants

@tfzee @nascheme @ngoldbaum @gpshead
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.