Repository navigation
Pool.imap(buffersize=...) blocks all other tasks on the pool until the iterator is consumed #158677
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Oct 3, 2026 - addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory3.16new features, bugs and security fixesnew features, bugs and security fixes
on Oct 3, 2026 PrakharAgarwal17 commented
on Oct 3, 2026 on Oct 3, 2026 · Hidden as low-qualityshow commentMore actions@PrakharAgarwal17 thanks for looking into this.
I had a patch in progress too and have just opened it as a draft: gh-158792.
I started with the same approach as you, submitting from the consumer's thread, but moved away from it.
next()then has to read the next input item before returning, so it can hold a ready result while the iterable is slow, and it deadlocks if the next item depends on that result being handled.The draft keeps the iterable in the task handler thread and makes the handler skip an iterator whose buffer is full instead of waiting on it.
On your questions:
- I left
close()unchanged and keptPool.imap(buffersize=...)silently drops results after close() #158675 separate, so each fix can be reviewed on its own. - With this approach, the question no longer comes up: the initial fill still happens in the task handler thread, so
imap()returns immediately as before.
Your review and testing on the draft would be very welcome. ;)
- I left
@picnixz, I closed my draft PR gh-158792 so we can agree on the approach here first.
The approach I used there is different from what I originally suggested in the report.
The task handler keeps consuming the input, as it does today. If one iterator hits its buffer limit, the handler skips it for now, processes other work, and comes back to it once a result is consumed. So from the caller's side, nothing changes.
I initially tried having
next()submit the next task, similar toExecutor.map(buffersize=...). The issue is that this also makesnext()consume the input in the caller's thread. If the input is slow,next()can block on the iterable even though a result is already ready.I'll wait for your take before pushing anything.
Bug report
Bug description:
While an
imap()orimap_unordered()iterator created withbuffersizeis waiting to be consumed, no other task submitted to the same pool is sent to the workers. If the program needs that other work before it reads more of the iterator, it deadlocks.Without
buffersize, this prints the ten pairs immediately.A second form, with a single buffered iterator:
ThreadPoolandimap_unordered()behave the same way.Cause: the pool has a single task handler thread (
Pool._handle_tasks). It iterates the generator returned by_guarded_task_generation(), and that generator blocks insema.acquire()when the buffer is full. While it is blocked there, the thread cannot take anything else from_taskqueue.Thread dump during the
ziphang:In the
zipexample,next(b)waits for a task that is never submitted, and the task handler waits forato be read, which never happens.Possible directions:
IMapIterator.next(), similar to whatExecutor.map(buffersize=...)does inconcurrent.futures.buffersizeiterator and other work.I would like to work on this. Before I write a patch, I would like to know which direction maintainers prefer.
Related: gh-158675 (results dropped after
close()), which comes from the same blockingsema.acquire()in the task handler.buffersizewas added in gh-64192 and is new in 3.16, so no released version is affected.CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs