Skip to content
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

✨ Add support for copying contextvars to thread workers #2160

Merged
merged 29 commits into from
Jan 31, 2022

Conversation

tiangolo
Copy link
Member

✨ Add support for copying contextvars to thread workers.

This is my attempt at implementing what's described here: #648

The original post said:

  • run_sync_in_worker_thread should do copy_context in the trio thread and then ctx.run(...) in the worker thread
    • And it should unset the sniffio magic contextvar in the child
  • BlockingTrioPortal should do copy_context in the blocking thread, and then use the context in Trio
    • But Trio doesn't currently have a way to say "use this context for this task", so I guess we'd need to add that. A context= argument to spawn_system_task would work, and we might as well add it to start_soon while we're at it.
      • I guess these will also want to set up the sniffio magic contextvar. Maybe they should always copy the context that's passed in, and then mutate the copy?

I looked at the release notes and realized these names have been deprecated/replaced by other things:

run_sync_in_worker_thread -> to_thread.run_sync
BlockingTrioPortal -> from_thread.run_sync and from_thread.run

I'm also assuming that the "sniffio magic contextvar" refers to sniffio.current_async_library_cvar.

@codecov
Copy link

codecov bot commented Oct 27, 2021

Codecov Report

Merging #2160 (02966f1) into master (53f6b74) will increase coverage by 0.04%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master    #2160      +/-   ##
==========================================
+ Coverage   99.57%   99.62%   +0.04%     
==========================================
  Files         114      115       +1     
  Lines       14733    14861     +128     
  Branches     2335     2339       +4     
==========================================
+ Hits        14671    14805     +134     
+ Misses         41       38       -3     
+ Partials       21       18       -3     
Impacted Files Coverage Δ
trio/_core/_run.py 100.00% <100.00%> (ø)
trio/_threads.py 100.00% <100.00%> (ø)
trio/tests/test_contextvars.py 100.00% <100.00%> (ø)
trio/tests/test_threads.py 100.00% <100.00%> (ø)
trio/tests/test_ssl.py 99.85% <0.00%> (+0.56%) ⬆️
trio/_highlevel_ssl_helpers.py 100.00% <0.00%> (+11.76%) ⬆️

@tiangolo
Copy link
Member Author

I'm not sure what to do about the Fedora CI broken build 🤔

I imagine this also requires an update in release notes, etc. But I would like to get some feedback first, check if anything seems wrong to anyone here, etc.

Copy link
Contributor

@richardsheridan richardsheridan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from my comments (one of which leaked out of this review oops!) I think the question of adding context kwargs to the API should be re-confirmed by a few others.

trio/_threads.py Show resolved Hide resolved
@tiangolo
Copy link
Member Author

Thanks for the thorough review @richardsheridan! ☕

Yep, I would have probably not included explicit context kwargs in the API, but I tried to stick to what @njsmith wanted/wrote in the original issue: #648

I'll wait for more feedback. 🤓

@pquentin
Copy link
Member

pquentin commented Oct 29, 2021

I merged master into your branch, this fixed the Fedora build!

trio/_threads.py Outdated
Comment on lines 248 to 249
func = cb
func_args = (q, fn, args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a more visual way to see the uncovered lines:

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I realized I'm already modifying the two calls that use this function, it's never called without a context, so I just made the context argument required, and that seems to have solved the coverage.

@tiangolo
Copy link
Member Author

Thanks for the reviews and comments @richardsheridan, @smurfix, and @pquentin! 🍰

I implemented the changes, tests, and updated the coverage.

I also just added the newsfragment and a bit of new simple documentation including a new simple example. Let me know what you think!

I think this would cover all the things necessary for the PR, am I missing something else?

@tiangolo
Copy link
Member Author

Awesome, thanks for the review @richardsheridan! I agree, I would love to have @njsmith's point of view on this!

@richardsheridan
Copy link
Contributor

If it were just me, I would merge the PR for the next release but without the kwarg change to Nursery.start_soon because it's redundant to context.run(nursery.start_soon) and a function signature change in the lowlevel package only is much more tolerable than in the core API. Also for what it's worth, it would keep things in sync with @agronholm choices in AnyIO.

@agronholm
Copy link
Contributor

If it were just me, I would merge the PR for the next release but without the kwarg change to Nursery.start_soon because it's redundant to context.run(nursery.start_soon) and a function signature change in the lowlevel package only is much more tolerable than in the core API. Also for what it's worth, it would keep things in sync with @agronholm choices in AnyIO.

I fully agree. @tiangolo would it be possible to modify this PR or create another one without the API changes?

@tiangolo
Copy link
Member Author

Awesome, thanks for the feedback @richardsheridan and @agronholm! 🙇‍♂️ 🚀

Done, I updated it accordingly. Let me know if you see anything else or if I missed something.

@richardsheridan
Copy link
Contributor

richardsheridan commented Jan 30, 2022

The failing tests will likely be fixed by merging master.

@@ -1540,7 +1543,7 @@ def task_exited(self, task, outcome):
################

@_public
def spawn_system_task(self, async_fn, *args, name=None):
def spawn_system_task(self, async_fn, *args, name=None, context=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is supposed to be here anymore? System tasks should always inherit the system context, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually haven't been able to make tests pass without it, I've been trying several different approaches, copying the context in different places, but in some I get errors that show it's not copying the right context, or thread timeout errors, or it breaks internals. 😱 I suspect it could be just that I lack the knowledge of the internals to do it in a different and better way.

Do you see a way that could be solved to remove it?

On the other hand, I understand @richardsheridan thinks this could be useful... for me it's the same, but I just don't see how to make it work without this. 🤔

@richardsheridan
Copy link
Contributor

richardsheridan commented Jan 30, 2022 via email

@tiangolo
Copy link
Member Author

I just noticed this warning in _core/_generated_run.py: https://github.com/python-trio/trio/blob/master/trio/_core/_generated_run.py#L1-L3

But I can't see what generates it, the only reference to generated_run I get searching in my editor is the import in _run.py, do you know what's that note about? Or what should I modify instead?

@richardsheridan
Copy link
Contributor

Right you should run gen_exports.py after making changes to public apis in _run.py and some others. It seemed like you were already doing that!

@agronholm
Copy link
Contributor

I think that's a good feature, otherwise there is no way to inject any other context to a system task...

On Sun, Jan 30, 2022, 11:10 AM Alex Grönholm @.> wrote: @.* commented on this pull request. ------------------------------ In trio/_core/_run.py <#2160 (comment)>: > @@ -1540,7 +1543,7 @@ def task_exited(self, task, outcome): ################ @_public - def spawn_system_task(self, async_fn, args, name=None): + def spawn_system_task(self, async_fn, args, name=None, context=None): I don't think this is supposed to be here anymore? System tasks should always inherit the system context, right? — Reply to this email directly, view it on GitHub <#2160 (review)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB5BHRSUGNPVHRB3ACB37SLUYVPGLANCNFSM5G3IHZUQ . You are receiving this because you were mentioned.Message ID: @.*>

That API addition is not mentioned in the news fragment.

@tiangolo
Copy link
Member Author

That API addition is not mentioned in the news fragment.

@agronholm get it, just updated it. I suspect only about trio.lowlevel.spawn_system_task, right? That's what I added to the newsfragment.

@pquentin pquentin merged commit d88acd2 into python-trio:master Jan 31, 2022
@tiangolo tiangolo deleted the contextvars-threads branch February 14, 2022 10:30
@tiangolo
Copy link
Member Author

Thanks @pquentin! 🚀

@oremanj
Copy link
Member

oremanj commented Feb 21, 2023

This PR introduced a regression: calling spawn_system_task now changes the current_async_library_cvar in the calling task too, even if that's in a different async library such as asyncio. This violates the rule that it is generally fine to do synchronous cross-calls between two event loops in the same thread without additional ceremony.

@richardsheridan
Copy link
Contributor

This PR introduced a regression: calling spawn_system_task now changes the current_async_library_cvar in the calling task too, even if that's in a different async library such as asyncio. This violates the rule that it is generally fine to do synchronous cross-calls between two event loops in the same thread without additional ceremony.

For my future self's reference: This was regression resulted in #2183 and was fixed in #2462.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants