Repository navigation
Conversation
|
Thank you! Unfortunately I don't know the multiprocessing code at all and I can't offer to review this. Maybe you can ask for help in the core development category on discuss.python.org. |
+ add ``Queue.shutdown` method, + update ``Queue.get``and ``Queue.put`` methods + add versionadded to ``get``and ``put``
Documentation build overview
460 files changed ·
|
Replace the pending-getter and pending-putter counters with a design which does not need to know who is waiting: * A count of the items accepted by put() in any process and not yet received tells a getter whether the queue is really empty, including the items still in another process's feeder thread buffer. * shutdown() writes a single empty message to the pipe. The getter which reads it writes it back, so every getter is woken in its turn, and raises ShutDown only when nothing is outstanding. * shutdown() releases the queue's semaphore once if the queue is full. A putter which acquires it and finds the queue shut down releases it again, waking the next one. * shutdown(immediate=True) discards the items in the pipe and in every feeder thread buffer, accounting for each of them. The unfinished task count of a JoinableQueue only goes down by the number of items discarded, as in queue.Queue. Items put just before shutdown() are no longer lost, getters and shutdown() itself no longer hang, and calling shutdown() again is allowed, as in queue.Queue. The tests are rewritten. Most of them also run on queue.Queue, through the 'threads' and 'manager' types, to check the behaviour is the same. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
|
Thanks for your patience on this one. While reviewing I found a few cases the pending-getter/putter counters could not handle, for example:
These came from the design rather than from individual bugs, so rather than ask you for another round I pushed Your docs, and NEWS entry are kept, the tests are rewritten to also cover the cases above. Reviews welcome. |
This adds
shutdown(immediate=False)tomultiprocessing.Queueandmultiprocessing.JoinableQueue, with the behaviour ofqueue.Queue.shutdown()(gh-104750) andasyncio.Queue.shutdown()(gh-104228), applied in every process which uses the queue.Behaviour
After
shutdown():put()raisesqueue.ShutDown. Callers blocked input()are unblocked and raise it too.get()returns the items already put, including those which a process has put but which its feeder thread has not yet written to the pipe. Once the queue is empty,get()raisesqueue.ShutDown, and callers blocked inget()are unblocked.immediate=True, the remaining items are discarded instead, in the pipe and in every process's feeder thread buffer. For aJoinableQueue, the unfinished task count goes down by the number of items discarded, so tasks which a worker has already taken still need theirtask_done().shutdown()may be called more than once, and an immediate shutdown may follow a graceful one.Implementation details from Claude
Two constraints shape it:
ctypesis not available on every platform, sono
multiprocessing.Value, andsem_getvalue()is broken on macOS, so asemaphore can only be tested for zero. The state is four
SemLocks:put()in any process and not yetreceived. This is what tells a getter that the queue is really empty,
rather than that the pipe happens to be empty;
tests at the same moment can give a wrong answer. Every test whose answer
matters is made under this lock, as is the "check the flag, count the
item" step of
put().Nothing needs to know how many processes are waiting:
shutdown()sends one empty message through the pipe, whichcan never be a valid pickle. The getter which reads it sends it back for
the next one, and raises
ShutDownonly if nothing is outstanding;otherwise it waits for the item which is still on its way.
shutdown()releases the queue's semaphore once if the queueis full. A putter which acquires it and finds the queue shut down releases
it again, which wakes the next one.
Feeder threads in other processes discard their buffers when they see the
flag. On Unix the caller holds the write lock while it empties the pipe
and leaves the marker in it, so an item which a feeder thread had already
decided to send lands behind the marker and is thrown away by the next
getter.
A queue which never calls
shutdown()pays for one lock and two semaphoreoperations per
put(), and one semaphore operation perget(). Asubclass which overrides
put()without counting its items keeps working.Limitations (documented)
get()may block instead of raising
ShutDown.shutdown(immediate=True)runs may still be returned by a laterget().Performance
Cost per item for a queue which is never shut down, producer process to consumer process, small items: