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-96471: Add multiprocessing queue shutdown by EpicWink · Pull Request #104230 · python/cpython

Conversation

@EpicWink

@EpicWink EpicWink commented May 6, 2023 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

Multiprocessing-only changes from #102499 (which supercedes #96474), updated to match the API introduced by #104750

Depends on #104750

  • Issue: Queue shutdown #96471

📚 Documentation preview 📚: https://cpython-previews--104230.org.readthedocs.build/

Sorry, something went wrong.

Uh oh!

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

All reactions
EpicWink and others added 2 commits May 6, 2023 15:10
@EpicWink
Add multiprocessing queue shutdown
75f6067
* Include docs
@YvesDup @EpicWink
Fix queue shutdown
3dd2415
* Remove ctypes import (use string identifiers for value types)
* Use queue's context to get shared value for queue state
* Include queue state in pickle
* Factor out queue-state checks and updates to methods
* Logic fixes in put and get
* Move shutdown method to before close
* Raise when shutting down closed queue
* Don't re-notify if immediately shutting down a queue already immediately shut-down
* Support shutdown in JoinableQueue
  * Handle in task_done and join
  * Logic fixes in put and shutdown
* Updated tests
* Document feature added in 3.13
@EpicWink
EpicWink requested a review from rhettinger as a code owner May 6, 2023 05:13
@bedevere-bot bedevere-bot mentioned this pull request May 6, 2023
Queue shutdown #96471
Open
@bedevere-bot bedevere-bot added the awaiting review label May 6, 2023
@blurb-it
📜🤖 Added by blurb_it.
7a85695
@EpicWink

EpicWink commented May 6, 2023

Copy link
Copy Markdown
Contributor Author

@willingc ready for review

All reactions

Sorry, something went wrong.

Uh oh!

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

This was referenced May 6, 2023
gh-96471: Add queue shutdown #96474
Closed
gh-96471: Add queue shutdown, next step. #102499
Closed
@gvanrossum gvanrossum added the topic-multiprocessing label May 6, 2023
@rhettinger
rhettinger removed their request for review May 6, 2023 19:16
YvesDup
YvesDup suggested changes May 9, 2023
View reviewed changes
Comment thread Lib/multiprocessing/queues.py Resolved

Uh oh!

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

@bedevere-bot bedevere-bot added awaiting core review awaiting review and removed awaiting review awaiting core review labels May 9, 2023
EpicWink added 3 commits May 9, 2023 20:36
@EpicWink
Add ShutDown exception to threading queues module
82364c2
@EpicWink
Merge remote-tracking branch 'upstream/main' into multiprocessing-que…
dc60eb3
…ue-shutdown
@EpicWink
Add references in docs and news entry
f5fcdd6
@EpicWink
EpicWink requested a review from gpshead as a code owner February 20, 2024 11:27
@EpicWink

EpicWink commented Feb 20, 2024

Copy link
Copy Markdown
Contributor Author

Like with #104750, we may want to modify the implementation of immediate=True to simply consume the queue rather than having a separate state.

YvesDup reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

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

@gvanrossum

gvanrossum commented Feb 22, 2024

Copy link
Copy Markdown
Member

Multiprocessing-only changes from #102499 (on top of #96474)

Depends on #104225

Looks like both those PRs were closed without merging -- maybe you can update the description? (I'm not super interested in the history of this PR any more, more in the similarity to what we've already added to queue.py.)

I also am unlikely going to be your reviewer for this PR -- I don't know who maintains multiprocessing these days, maybe @gpshead feels confident. (I've never even used it and neither have I ever read the code before or reviewed any part of it. :-)

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink
EpicWink marked this pull request as draft February 22, 2024 01:13
@bedevere-app bedevere-app Bot removed the awaiting review label Feb 22, 2024
EpicWink added 2 commits March 22, 2024 15:03
@EpicWink
Merge remote-tracking branch 'upstream/main' into multiprocessing-que…
ae34ddb
…ue-shutdown
@EpicWink
Update docs, add to what's-new
ee8d4df
@EpicWink
EpicWink force-pushed the multiprocessing-queue-shutdown branch 2 times, most recently from d1ceccf to 90279ad Compare March 26, 2024 09:08
@YvesDup

YvesDup commented Apr 9, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

Hi @EpicWink ,

I've just done a quick review of the code (I have seen it is a draft). I have got mainly 3 comments:

  • In the __init__ method, I am not sure about the lock=self._rlock argument in the ctx.Value instruction. When I run shutdown method on a new queue, my script hangs at the self._is_shutdown.value = True instruction. If I remove the last lock=self._rlock argument, there is no hang. I can not explain why ? Maybe see documentation here
  • Used of qsize method of Queue class is not recommended because it is not implemented on Mac OSX. The self._sem.release(self.qsize()) instruction of shutdown method should be replace.
  • Always in this method, the CM with self._notempty: self._notempty.notify_all() has to be move one block left to be executed whatever the immediate value (getters unlock case).

I run my tests on a Mac OSX 13.4 m1.
I hope this will be helpfull

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink

EpicWink commented Apr 10, 2024

Copy link
Copy Markdown
Contributor Author

@YvesDup this PR is still a work in progress

YvesDup reacted with eyes emoji
All reactions
  • 👀 1 reaction

Sorry, something went wrong.

Uh oh!

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

@EpicWink
EpicWink force-pushed the multiprocessing-queue-shutdown branch from 90279ad to 19ab554 Compare April 12, 2024 07:05
@EpicWink
WIP: consume queue on immediate shutdown
42fbea6
@EpicWink
EpicWink force-pushed the multiprocessing-queue-shutdown branch from 19ab554 to 42fbea6 Compare April 12, 2024 07:08
@EpicWink

EpicWink commented Apr 12, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor Author

In the init method, I am not sure about the lock=self._rlock argument in the ctx.Value instruction. When I run shutdown method on a new queue, my script hangs at the self._is_shutdown.value = True instruction. If I remove the last lock=self._rlock argument, there is no hang. I can not explain why ? Maybe see documentation here

@YvesDup I had assumed _rlock would be an instance of RLock, but that's not the case; I think it is short for "read lock". I'll have _is_shutdown use its own lock.


Used of qsize method of Queue class is not recommended because it is not implemented on Mac OSX. The self._sem.release(self.qsize()) instruction of shutdown method should be replace.

Without some way to determine the number of putters to unblock, shutdown() can't work. I'll use _maxsize.


Always in this method, the CM with self._notempty: self._notempty.notify_all() has to be move one block left to be executed whatever the immediate value (getters unlock case).

The threading.Condition _notempty is used to tell the feeder thread to check whether there are items to send, not terribly relevant here: I'll remove it.

I need to find some other way to wake up getters and have them check empty then shutdown, otherwise they'll be blocked on connection.recv_bytes() forever.

All reactions

Sorry, something went wrong.

Uh oh!

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

@YvesDup

YvesDup commented Apr 12, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

In the init method, I am not sure about the lock=self._rlock argument in the ctx.Value instruction. When I run shutdown method on a new queue, my script hangs at the self._is_shutdown.value = True instruction. If I remove the last lock=self._rlock argument, there is no hang. I can not explain why ? Maybe see documentation here

@YvesDup I had assumed _rlock would be an instance of RLock, but that's not the case; I think it is short for "read lock". I'll have _is_shutdown use its own lock.

That seems good to me. Default lock should be a RLock.

Used of qsize method of Queue class is not recommended because it is not implemented on Mac OSX. The self._sem.release(self.qsize()) instruction of shutdown method should be replace.

Without some way to determine the number of putters to unblock, shutdown() can't work. I'll use _maxsize.

Would be possible to call self._sem.release() instruction in the _clear method loop for each item to remove ?

Always in this method, the CM with self._notempty: self._notempty.notify_all() has to be move one block left to be executed whatever the immediate value (getters unlock case).

The threading.Condition _notempty is used to tell the feeder thread to check whether there are items to send, not terribly relevant here: I'll remove it.

Good catch !

I have just seen that unittest tests run well, except in Mac OS (due to the qsize() method). Nice

update: I was too much in hurry, some tests failed, not only on Mac. :-(

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink

EpicWink commented Apr 15, 2024

Copy link
Copy Markdown
Contributor Author

Would be possible to call self._sem.release() instruction in the _clear method loop for each item to remove ?

@YvesDup No, _clear is for removing items from the queue when immediate is true, and is not relevant to unblocking putters.

Thinking about it, using _maxsize doesn't work anyway because there can be more putters than the queue's maximum size.

All reactions

Sorry, something went wrong.

Uh oh!

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

@YvesDup

YvesDup commented Apr 16, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

Hi @EpicWink

I've done a lot of tests over the last few days. It's very difficult to work the code to add the 'shutdown' features. I've succeeded to update the get parts of the feature (with a multiprocessing.Condition), but it's not really maintainable (not only my fault).
I wonder if it wouldn't be better to rewrite the class itself using Condition synchronizations (as a same model as the threading.Queue class) in order to handle full and empty queue states. This will also be very useful to manage easily pending get/put processes.
IMO, this work is mandatory before adding all 'shutdown' features. I think it's really worth it.
Thanks for your comments, ideas, thoughts.

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink

EpicWink commented Apr 17, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor Author

It's very difficult to work the code to add the 'shutdown' features.

@YvesDup I agree, it wasn't designed for interruptable gets.


I wonder if it wouldn't be better to rewrite the class itself using Condition synchronizations (as a same model as the threading.Queue class) in order to handle full and empty queue states. This will also be very useful to manage easily pending get/put processes.

Instead, it might be better to create a new multiprocessing.queues.TerminableQueue class with the same API as the threading queue.Queue (ie no feed-thread methods like close). If this becomes popular, multiprocessing.queues.Queue could be replaced with it. I think this would need more discussion (in Discourse).


IMO, this work is mandatory before adding all 'shutdown' features.

Perhaps, I think the current self._reader.recv_bytes() could be wrapped with a select() called and combined with a timeout.

All reactions

Sorry, something went wrong.

Uh oh!

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

@YvesDup

YvesDup commented Apr 18, 2024

Copy link
Copy Markdown
Contributor

Instead, it might be better to create a new multiprocessing.queues.TerminableQueue class with the same API as the threading queue.Queue (ie no feed-thread methods like close). If this becomes popular, multiprocessing.queues.Queue could be replaced with it. I think this would need more discussion (in Discourse).

It's good idea, because multiprocessing.queues.Queue is used in many others parts of mutiprocessing as Pool. It is a critical tool. So let's go to Discourse about your proposition.

Perhaps, I think the current self._reader.recv_bytes() could be wrapped with a select() called and combined with a timeout.

select() is really low level, IMO this should be encapsulated in a (derivated) class.

For my side, I have been played with a Condition to handle get part, in replacement of the self._rlock Lock.
The partial get code looks like this :

        if block and timeout is None:
            with self._rcond:
                self._rcond.wait_for(self._notempty_or_shutdown_and_empty, 0.001) # arbitrary timeout
                if self._is_shutdown.value and self.empty():
                    raise ShutDown
                res = self._recv_bytes()

That seems to work well, but I haven't tested all the use cases, specialy with a timeout or a non blocking get.
I wonder if I'll go on this way ?

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink

EpicWink commented Apr 19, 2024

Copy link
Copy Markdown
Contributor Author

select() is really low level, IMO this should be encapsulated in a (derivated) class.

@YvesDup see selectors


For my side, I have been played with a Condition to handle get part, in replacement of the self._rlock Lock.
...
That seems to work well, but I haven't tested all the use cases, specialy with a timeout or a non blocking get.
I wonder if I'll go on this way ?

Why do you say this is "not really maintainable"?

All reactions

Sorry, something went wrong.

Uh oh!

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

@YvesDup

YvesDup commented Apr 22, 2024

Copy link
Copy Markdown
Contributor

For my side, I have been played with a Condition to handle get part, in replacement of the self._rlock Lock.
...
That seems to work well, but I haven't tested all the use cases, specialy with a timeout or a non blocking get.
I wonder if I'll go on this way ?

Why do you say this is "not really maintainable"?

II said that because there will be a lot of modifications to make to this class before we have something well finished and therefore "maintainable" in the future. I was really embarrassed not to have an OS-compatible semaphore where we could get the current value. And it's a pain to release pending "put" processes.

Whatever we decide, the most critical point seems to me to be managing the size of the queue (or the number of elements), with the wobbly semaphore or with an internal counter.

PS: I'll be away for the next 2 weeks.

All reactions

Sorry, something went wrong.

Uh oh!

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

@gvanrossum

gvanrossum commented Apr 22, 2024

Copy link
Copy Markdown
Member

Hey guys, do I smell a bit of scope creep here? The code is complex, yes, but I worry that creating a new implementation will just add more complexity to the multiprocessing package rather than simplifying anything. I am not hopeful that people will switch to a new queue class with sufficient enthusiasm that we will ever be able to replace the old one with confidence.

Would it perhaps be better to focus on the ability to shut down a queue without the "immediate" flag? That might be implemented just by transmitting a sentinel (which seems already supported). Or possibly "immediate=True" could be implemented in a "best effort" way, just flushing what's in the local buffer (you can't control what other processes have already buffered anyways).

In any case it looks like it's best if you all take your time with this project -- don't rush it just because the 3.13 feature freeze is just around the corner. It may also indeed be a good idea to start a discussion on Discourse to see if there are others interested in helping out with the architecture of this feature for multiprocessing.

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink

EpicWink commented Apr 23, 2024

Copy link
Copy Markdown
Contributor Author

Hey guys, do I smell a bit of scope creep here?

@gvanrossum yes, I wanted to discuss in Discourse: https://discuss.python.org/t/queue-termination-further-design/26630/13


The code is complex, yes, but I worry that creating a new implementation will just add more complexity to the multiprocessing package rather than simplifying anything. I am not hopeful that people will switch to a new queue class with sufficient enthusiasm that we will ever be able to replace the old one with confidence.

I agree, I would prefer to make small changes to the behaviour of the existing queue class to support shutdown.

At the time, my thinking was to create a new class which could be drop-in replaced, with some methods with no-ops (the feeder-thread managing methods), which will in some future release replace the existing queue class. I don't think this is worth it just for shutdown (especially as process termination is already easy).


Would it perhaps be better to focus on the ability to shut down a queue without the "immediate" flag?

No, the problem here is unblocking getters on shutdown, which needs to happen regardless of immediate's value. immediate=True is actually already implemented in this pull-request in a similar way to the other queues.


In any case it looks like it's best if you all take your time with this project -- don't rush it just because the 3.13 feature freeze is just around the corner.

I agree, especially because of what I said before: it's already easy to terminate worker/feeder processes (outside of queues).


It may also indeed be a good idea to start a discussion on Discourse to see if there are others interested in helping out with the architecture of this feature for multiprocessing.

Also linked above


For my side, I have been played with a Condition to handle get part, in replacement of the self._rlock Lock.

@YvesDup why are you setting a timeout in the following? I would think you simply wait for _rcond to be notified.

self._rcond.wait_for(self._notempty_or_shutdown_and_empty, 0.001)

Whatever we decide, the most critical point seems to me to be managing the size of the queue (or the number of elements), with the wobbly semaphore or with an internal counter.

A pretty standard pattern:

self._n_items = ctx.Value("Q", 0)
...
with self._n_items.get_lock():
    self._n_items += 1  # or -= 1 in get()
YvesDup reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

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

@gvanrossum

gvanrossum commented Apr 23, 2024

Copy link
Copy Markdown
Member

Thanks for the update. Somehow I missed that Discourse issue (maybe because I've muted the Ideas tag as a whole). Do you want me to pipe up there? It's been 5d since your update and you haven't gotten any feedback yet.

Also happy to sit back and relax -- you two seem to be able to duke out any problem thrown at you.

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink

EpicWink commented Apr 23, 2024

Copy link
Copy Markdown
Contributor Author

Do you want me to pipe up there?

@gvanrossum no thanks, we'll just figure something out for 3.14.

All reactions

Sorry, something went wrong.

Uh oh!

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

@YvesDup

YvesDup commented May 14, 2024

Copy link
Copy Markdown
Contributor

Hi @EpicWink , how are you ?
I am back and available to move forward and finish this PR if you wish.

EpicWink reacted with thumbs up emoji YvesDup reacted with laugh emoji
All reactions
  • 👍 1 reaction
  • 😄 1 reaction

Sorry, something went wrong.

Uh oh!

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

@YvesDup

YvesDup commented May 27, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

For these last weeks, I've been doing a lot of thinking and testing to find the simplest solution for managing pending processes when the queue shuts down.

For pending “put” processes, they are blocked by the _sem BoundedSemaphore. Currently, there's no way of knowing how many processes are pending. If we had this number, we could call as many release method as necessary. An alternative would be to set up a Condition to manage these processes and unblock them with a notify_all. But this would mean rewriting the put method. Not so simple.

As for pending “get” processes, they are blocked by the _recv_bytes method. Here again, their number is not known. To prevent them from being blocked, I first thought of rewriting the code using a Condition and its wait method. The wait would be exited only when an item is available. Useful, but it requires a lot of modifications to an already complex code.
A another way to unblock a _recv_bytes, is to put an item into the pipe. We could therefore send dummy data (as the sentinel data used in stopping _feed thread part) to unblock them. Whe unblocked in put, we have just to raise a ShutDown. That seems simple and minimalist with a small impact on the current get method. You just need to know how many processes are pending.

To implement these 2 cases, all you need are two waiting process counters, in the form of a shared array named _n_pendings. Now when the shutdown method is called, I can release all pending processes and empty the queue if the shutdown is immediate.

The proposed code here passes all current unittests on Mac/Linux only for standard python (no free-threading).
I've added dedicated unit tests to the _test_multiprocessing.py test file.

This working version is available here on that last commit in my working branch. I hope I'm on a good path.
I look forward to your feedback, and am open to any suggestions.

Note: I`ve just updated my last commit

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink
Merge remote-tracking branch 'upstream/main' into multiprocessing-que…
1f19257
…ue-shutdown
@YvesDup

YvesDup commented May 28, 2024

Copy link
Copy Markdown
Contributor

FYI all tests on my working branch, including free-threading, are succeeded.

All reactions

Sorry, something went wrong.

Uh oh!

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

@YvesDup

YvesDup commented Jun 6, 2024

Copy link
Copy Markdown
Contributor

Perhaps, I think the current self._reader.recv_bytes() could be wrapped with a select() called and combined with a timeout.

@EpicWink Have you done some tests about this ?

All reactions

Sorry, something went wrong.

Uh oh!

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

@EpicWink

EpicWink commented Jun 12, 2024

Copy link
Copy Markdown
Contributor Author

Perhaps, I think the current self._reader.recv_bytes() could be wrapped with a select() called and combined with a timeout.

@EpicWink Have you done some tests about this ?

I looked into it, I don't think it could work

All reactions

Sorry, something went wrong.

Uh oh!

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

@YvesDup

YvesDup commented Jun 14, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

@EpicWink Did you watch my proposition that I've posted last month ?

All reactions

Sorry, something went wrong.

Uh oh!

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

@YvesDup YvesDup mentioned this pull request Oct 22, 2024
Multiprocessing Semaphore and BoundedSemaphore - incomplete features ('get_value' missing) on MacOSX. #125828
Open
@EpicWink
Merge remote-tracking branch 'upstream/main' into multiprocessing-que…
e1654d4
…ue-shutdown
@YvesDup

YvesDup commented Sep 12, 2025

Copy link
Copy Markdown
Contributor

Hi @gvanrossum @EpicWink
I have just posted a new PR (#138828) about a new "multiprocessing queue shutdown" solution.
I will be glad to read your feedbacks
Thank you

All reactions

Sorry, something went wrong.

Uh oh!

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

@github-actions

github-actions Bot commented Apr 12, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

All reactions

Sorry, something went wrong.

Uh oh!

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

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@gpshead gpshead Awaiting requested review from gpshead gpshead is a code owner
@AA-Turner AA-Turner Awaiting requested review from AA-Turner AA-Turner will be requested when the pull request is marked ready for review AA-Turner is a code owner
1 more reviewer
@YvesDup YvesDup YvesDup requested changes
Reviewers whose approvals may not affect merge requirements

Uh oh!

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

Assignees

No one assigned

Labels

stale Stale PR or inactive for long period of time. topic-multiprocessing

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

@EpicWink @gvanrossum @YvesDup @bedevere-bot
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.