-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[#9719] Support contextvars in coroutines #1192
Conversation
This looks substantially complete to me, modulo maybe some minor test issues. Should it be out of draft / in review? |
Hi, I'm looking for such capability! Is there any plan for merging this PR? |
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.
Update: I'm an idiot, I read the test backwards, this was all wrong
I think these semantics will be extremely surprising to most users.
In asyncio
(and abstractly, in PEP 567) the context follows the Task, not the Future.
This is what I think most people want when they want context. For example, consider the following
async def do_work(request_id):
with some_context_for_logging(request_id): # manipulate a context var
do_something_that_logs_stuff()
api_value_1 = await some_remote_api()
api_value_2 = await other_remote_api()
do_other_thing_that_logs_stuff()
# elsewhere, in framework land
def other_remote_api():
with some_context_for_logging(not_my_request_id):
get_set_up()
deferred = make_actual_api_request()
return deferred
I think we can agree that if do_other_thing_that_logs_stuff
would really want to get request_id
and not not_my_request_id
associated with its logs, even though the coroutine has unsuspended due to other_remote_api
waking it up.
More importantly, if we were to api_value_1, api_value_2 = await gatherResults([some_remote_api(), other_remote_api()])
instead, small differences in both the implementation of gatherResults
and the order of results being returned might affect the context values.
I hate to send this back to the drawing board entirely, but reading test_contextvarsWithAsyncAwait
really confirmed my suspicions that this is, unfortunately, not a useful way to propagate async context between coroutines.
FWIW: this is the reason that this feature never really made it in stock Twisted: you really want context to follow the abstract notion of the "task" that you're performing, but without a coroutine, it's hard to tell where that really is, and there are lots of these edge cases which very clearly make it not the callback chain of an individual Deferred
object.
My inclination would be to do a more limited version of coroutine support which works exclusively with ensureDeferred
— then, maybe, accidentally have it work with @inlineCallbacks
too, if that's easy.
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.
Okay way smaller changes this time.
Top line feedback:
I think we should use an active-nothing pattern and "emulate" the Context object when it's not present (mostly by doing nothing), especially given that it's so easy to do, and that it can't possibly be much of a perf win to avoid it since we'd only be avoiding it on older versions anyway. This would also simplify the implementation a little, which is nice, given that the code here is already pretty gnarly and subtle.
If you feel strongly for the current implementation strategy, then we should be carefully is None
-ing everywhere so we don't pay for extra method-dispatch overhead on every single call (__bool__
/ __nonzero__
) when we absolutely never want to be doing that anyway.
Some IRC discussion that might be interesting for posterity:
|
@@ -0,0 +1 @@ | |||
twisted.internet.defer.inlineCallbacks and ensureDeferred will now capture the current contextvars context when ran, and restore the context when resuming the coroutine between yields. This functionality requires Python 3.7+, or the contextvars PyPI backport to be installed for Python 3.5-3.6. |
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.
grammar / agreement nitpick: "when ran" ➡️ "when running"
(also: pretty sure contextvars only supports 3.6)
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.
the backport I mean
L G T M. Will merge. |
Ticket: https://twistedmatrix.com/trac/ticket/9719