-
-
Notifications
You must be signed in to change notification settings - Fork 31.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-131466: concurrent.futures.Executor.map
: avoid temporarily exceeding buffersize
while collecting the next result
#131467
base: main
Are you sure you want to change the base?
gh-131466: concurrent.futures.Executor.map
: avoid temporarily exceeding buffersize
while collecting the next result
#131467
Conversation
…llecting the next result
…Test.test_free_reference
concurrent.futures.Executor.map
: avoid temporarily exceeding buffersize
while collecting the next resultconcurrent.futures.Executor.map
: avoid temporarily exceeding buffersize
while collecting the next result
Lib/concurrent/futures/_base.py
Outdated
yield _result_or_cancel(fs.pop(), end_time - time.monotonic()) | ||
|
||
# Yield the awaited result | ||
yield fs.pop().result() |
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.
to be discussed: this could be replaced by a lighter yield fs.pop()._result
because the prior call to _result_or_cancel
guarantees that at this point the result is available.
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.
While I understand that we could possibly exceed buffersize
while collecting the next result, is there a real-word use case where it would really cause an issue? the reason is that we access to fs[-1]
and then do fs.pop()
.
I see that have a del fut
in _result_or_cancel()
but can you confirm that it's sufficient to not hold any reference to the yet-to-be-popped future?
Asking Gregory as well since he's the mp expert c: |
@picnixz sorry I re-asked your review because you made me realize that we actually don't need
I'm digging deeper into #95169 's context to check if I miss any non-tested scenario, especially regarding this:
|
yes, that's what I wanted to ask, but I'm not an expert here so i'll let you investigate first c: |
fut.cancel() | ||
finally: | ||
# Break a reference cycle with the exception in self._exception | ||
del fut |
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.
Hi @graingert!
Context:
As a side effect, this PR may remove the need for _result_or_cancel
(introduced in #95169). If fetching the next result raises a TimeoutError
, its future will still be in fs
and will be properly cancelled by the result_iterator
's finally
block.
Question:
Do you remember in which scenario the del fut
was required? Removing it in the current main
does not break any tests 🤔
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.
This is if fut.result() raises an exception there's a reference cycle where fut.exception().__traceback__ -> fut.exception()
Probably worth adding a test, a git grep for no_other_refs
will find a similar one
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.
Thank you! Will add the test 🫡
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.
fyi #131701 adds the test @graingert @picnixz 🙏🏻
Context recap:
If we have:
What happens when calling
next(results)
:arg
frominterable
and put a task forfn(arg)
in the buffer-> During step 2. there is
buffersize + 1
buffered tasks.This PR swaps steps 1. and 2. so that
buffersize
is never exceeded, even duringnext
.concurrent.futures.Executor.map
temporarily exceeds itsbuffersize
while collecting the next result #131466