-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[#9719] Support contextvars in coroutines #1192
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
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 deferredI 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.
glyph
left a comment
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