-
-
Notifications
You must be signed in to change notification settings - Fork 345
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
Deprecate BlockingTrioPortal in favor of from_thread.run and from_thread.run_sync #1122
Conversation
This addressses issue #810 by implementing `trio.from_thread.run` and `trio.from_thread.run_sync` and deprecating `BlockingTrioPortal`. Support re-entering the same `Trio.run()` loop by stashing the current trio token in Thread Local Storage right before `run_sync_in_thread` spawns a worker thread. When `trio.from_thread.run_sync` or `trio.from_thread.run` are called in this thread, they can access the token and use it to re-enter the `Trio.run()` loop. This commit deprecates `BlockingTrioPortal`. For the majority of use cases, using the thread local Trio Token should be sufficient. If for any reason, another special Trio Token needs to be used, it can be passed as a kwarg to `from_thread.run` and `from_thread.run_sync`. Here is a snippet from how the new API works: ```python3 import trio def thread_fn(): start = trio.from_thread.run_sync(trio.current_time) print("In Trio-land, the time is now:", start) trio.from_thread.run(trio.sleep, 1) end = trio.from_thread.run_sync(trio.current_time) print("And now it's:", end) async def main(): await trio.run_sync_in_thread(thread_fn) trio.run(main) ``` Here is how the same code works in "old" Trio: ```python3 import trio def thread_fn(token): portal = trio.BlockingTrioPortal(token) start = portal.run_sync(trio.current_time) print("In Trio-land, the time is now:", start) portal.run(trio.sleep, 1) end = portal.run_sync(trio.current_time) print("And now it's:", end) async def main(): token = trio.hazmat.current_trio_token() await trio.run_sync_in_thread(thread_fn, token) trio.run(main) ```
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 a great start! Making the trio_token
an optional argument is probably better than the complicated thing that I was suggesting.
Some boring comments:
-
We need to update the docs too. Generally when we deprecate something, we remove it from the docs immediately. (Anyone still using the old deprecated thing can look at the old docs if they really need them, and new users don't need to know about the old deprecated stuff.) So basically, you'll want to search for
BlockingTrioPortal
, and everywhere it shows up convert the docs to talk abouttrio.from_thread
instead. -
We'll need a newsfragment to tell people about the change. In this case, we already have a
810.removal.rst
, so I guess we'll want to update that... we can figure out the details here at the end once the rest is sorted out, but I wanted to let you know it was coming :-).
Regarding the code itself:
-
We have a bunch of tests that use
BlockingTrioPortal
incidentally, or that are testing edge cases ofBlockingTrioPortal
. We should switch these to testtrio.from_thread
instead! Basically just a search-and-replace job I guess. -
Once we've done that, codecov will complain that we've got lots of untested code in
BlockingTrioPortal
. To keep duplication down, we might as well delete all that code and switch it to usingtrio.from_thread
instead... like,BlockingTrioPortal.__init__
will still want to capture a trio token, and thenBlockingTrioPortal.run
will just be a call tofrom_thread.run(..., trio_token=self._token)
, and ditto forrun_sync
.
Let us know if you have any questions!
trio/_threads.py
Outdated
``run_sync_in_thread`` also injects the current ``TrioToken`` into the | ||
spawned thread's local storage so that these threads can re-enter the Trio | ||
loop by calling either :func: ``trio.from_thread.run`` or | ||
:func: ``trio.from_thread.run_sync`` for async or synchronous functions, |
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.
ReStrustructured Text format is a little weird: double-backticks, like ``trio.from_thread.run``
, mean raw literal code – it's the equivalent of markdown's single-backticks. If you want to use :func:
to link to a named function, then you have to use single-backticks: :func:`trio.from_thread.run`
.
But, since #1091 was merged recently, we don't even need to write :func:
most of the time – if you just plain single-backticks, like `trio.from_thread.run`
, then sphinx will now automatically find the object and link to it – you don't have to use :func:
to specify the type.
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.
Thanks for the hint! Yeah, I have never used RST, only Markdown, and also never Sphinx either so the docs are a bit of a learning curve for me.
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.
It looks like these double backticks still need to be replaced with single backticks.
Rework thread local storage to use the canonical threading.local() accessor, update from_thread.* unit tests to better reflect use cases, updated no token error msg to give a specific reason for failure.
Updated documentation and replaced examples containing `BlockingTrioPortal` to now reference `trio.from_thread.run` and `trio.from_thread.run_sync`. Removed documentation on `BlockingTrioPortal`.
Migrate all uses of `BlockingTrioPortal` in `test_thread.py` to use `trio.from_thread.*` API instead.
Codecov Report
@@ Coverage Diff @@
## master #1122 +/- ##
==========================================
- Coverage 99.51% 99.51% -0.01%
==========================================
Files 104 105 +1
Lines 12631 12711 +80
Branches 968 969 +1
==========================================
+ Hits 12570 12649 +79
- Misses 40 41 +1
Partials 21 21
|
Codecov Report
@@ Coverage Diff @@
## master #1122 +/- ##
==========================================
+ Coverage 99.54% 99.55% +<.01%
==========================================
Files 104 105 +1
Lines 12646 12715 +69
Branches 967 969 +2
==========================================
+ Hits 12589 12658 +69
Misses 36 36
Partials 21 21
|
Applied `fixup_module_metadata` to `trio.from_thread` and corrected most Sphinx build warnings.
Fixed issue where `RunFinishedError` was secretly refrencing deprecated `BlockingTrioPortal`
Found the remaining RST linting errors thanks to @Fuyukai and the trio gitter.im. Thanks!
Add back in some unit tests for the legacy `BlockingTrioPortal` to ensure that the new `trio.from_thread` can handle being called from `portal.run()` and `portal.run_sync()`.
Fixed naming conflict of `test_do_in_trio_thread_from_trio_thread`.
Okay, hello again! I have fixed up all of the issues mentioned in the last round of the PR and have also made sure all of the documentation and code coverage is at par with or better than where it was before. |
@njsmith mind giving any feedback on this? I think I made all of the requested changes but want to confirm before I merge this. |
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.
Sorry for the slow follow up here.
This is looking good overall, so now you get all the fiddly little comments to polish up the details :-)
trio/_threads.py
Outdated
``run_sync_in_thread`` also injects the current ``TrioToken`` into the | ||
spawned thread's local storage so that these threads can re-enter the Trio | ||
loop by calling either :func: ``trio.from_thread.run`` or | ||
:func: ``trio.from_thread.run_sync`` for async or synchronous functions, |
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.
It looks like these double backticks still need to be replaced with single backticks.
Updating this branch with changes made in master
@njsmith Got some more time to polish this up, can you let me know if the latest round of changes are up to spec? Thanks as always! |
@epellis Looks great, thanks! BTW, I think github is listing your last few commits as coming from your work account – doesn't matter to me, but FYI in case you wanted to keep those separate. Any thoughts on what you want to work on next? |
@njsmith Thanks for letting me know, I thought I signed out of that account on this laptop but I guess my git cache says otherwise 🤷♂. I'd love to continue working with the scheduler and threading/async interop so I'll take stock of how the rest of #1085 and friends are doing and try to comment there in the next week or so with chunks I think I could manage. Thanks again for your feedback, this PR was a really enjoyable process! :) |
When it was deprecated in python-triogh-1122, it accidentally got moved into trio.hazmat. Fixes: python-triogh-1167
When it was deprecated in python-triogh-1122, it accidentally got moved into trio.hazmat. Fixes: python-triogh-1167
This addressses issue #810 by implementing
trio.from_thread.run
andtrio.from_thread.run_sync
and deprecatingBlockingTrioPortal
. There is probably some more stuff we need to add before this PR is fully complete, like integrating #1098 and therun_sync_soon
API, but this is a good start.Support re-entering the same
Trio.run()
loop by stashing the currentTrio Token in Thread Local Storage right before
run_sync_in_thread
spawns a worker thread. When
trio.from_thread.run_sync
ortrio.from_thread.run
are called in this thread, they can access thetoken and use it to re-enter the
Trio.run()
loop.This commit deprecates
BlockingTrioPortal
. For the majority ofuse cases, using the thread local Trio Token should be sufficient. If
for any reason, another Trio Token needs to be used, it can be
passed as a kwarg to
from_thread.run
andfrom_thread.run_sync
.Here is a snippet from how the new API works:
Here is how the same code works in "old" Trio: