Skip to content

Commit e1da9d2

Browse files
committed
[trio.from_thread]
Updated documentation and replaced examples containing `BlockingTrioPortal` to now reference `trio.from_thread.run` and `trio.from_thread.run_sync`. Removed documentation on `BlockingTrioPortal`.
1 parent 26d4b02 commit e1da9d2

File tree

4 files changed

+27
-118
lines changed

4 files changed

+27
-118
lines changed

docs/source/reference-core.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,7 +1465,7 @@ for working with real, operating-system level,
14651465
need to push some blocking I/O into a thread, there's
14661466
:func:`run_sync_in_thread`. And if you're in a thread and need
14671467
to communicate back with Trio, you can use a
1468-
:class:`BlockingTrioPortal`.
1468+
`trio.from_thread`.
14691469

14701470

14711471
.. _worker-thread-limiting:
@@ -1603,14 +1603,15 @@ Putting blocking I/O into worker threads
16031603
Getting back into the Trio thread from another thread
16041604
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16051605

1606-
.. autoclass:: BlockingTrioPortal
1607-
:members:
1606+
.. autofunction:: from_thread.run
1607+
1608+
.. autofunction:: from_thread.run_sync
16081609

16091610
This will probably be clearer with an example. Here we demonstrate how
16101611
to spawn a child thread, and then use a :ref:`memory channel
16111612
<channels>` to send messages between the thread and a Trio task:
16121613

1613-
.. literalinclude:: reference-core/blocking-trio-portal-example.py
1614+
.. literalinclude:: reference-core/from-thread-example.py
16141615

16151616

16161617
Exceptions and warnings

docs/source/reference-core/blocking-trio-portal-example.py renamed to docs/source/reference-core/from-thread-example.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
import trio
22

3-
def thread_fn(portal, receive_from_trio, send_to_trio):
3+
4+
def thread_fn(receive_from_trio, send_to_trio):
45
while True:
56
# Since we're in a thread, we can't call methods on Trio
67
# objects directly -- so we use our portal to call them.
78
try:
8-
request = portal.run(receive_from_trio.receive)
9+
request = trio.from_thread.run(receive_from_trio.receive)
910
except trio.EndOfChannel:
10-
portal.run(send_to_trio.aclose)
11+
trio.from_thread.run(send_to_trio.aclose)
1112
return
1213
else:
1314
response = request + 1
14-
portal.run(send_to_trio.send, response)
15+
trio.from_thread.run(send_to_trio.send, response)
16+
1517

1618
async def main():
17-
portal = trio.BlockingTrioPortal()
1819
send_to_thread, receive_from_trio = trio.open_memory_channel(0)
1920
send_to_trio, receive_from_thread = trio.open_memory_channel(0)
2021

2122
async with trio.open_nursery() as nursery:
2223
# In a background thread, run:
2324
# thread_fn(portal, receive_from_trio, send_to_trio)
2425
nursery.start_soon(
25-
trio.run_sync_in_thread,
26-
thread_fn, portal, receive_from_trio, send_to_trio
26+
trio.run_sync_in_thread, thread_fn, receive_from_trio, send_to_trio
2727
)
2828

2929
# prints "1"
@@ -40,4 +40,5 @@ async def main():
4040
# When we exit the nursery, it waits for the background thread to
4141
# exit.
4242

43+
4344
trio.run(main)

docs/source/reference-hazmat.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,11 +339,11 @@ These transitions are accomplished using two function decorators:
339339
function).
340340

341341
An example of where you'd use this is in implementing something
342-
like :meth:`trio.BlockingTrioPortal.run`, which uses
342+
like :meth:`trio.from_thread.run`, which uses
343343
:meth:`TrioToken.run_sync_soon` to get into the Trio
344344
thread. :meth:`~TrioToken.run_sync_soon` callbacks are run with
345345
:exc:`KeyboardInterrupt` protection enabled, and
346-
:meth:`~trio.BlockingTrioPortal.run` takes advantage of this to safely set up
346+
:meth:`~trio.from_thread.run` takes advantage of this to safely set up
347347
the machinery for sending a response back to the original thread, but
348348
then uses :func:`disable_ki_protection` when entering the
349349
user-provided function.

trio/_threads.py

Lines changed: 12 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -21,109 +21,16 @@
2121

2222

2323
class BlockingTrioPortal:
24-
"""A portal that synchronous threads can reach through to run code in the
25-
Trio thread.
26-
27-
Most Trio functions can only be called from the Trio thread, which is
28-
sometimes annoying. What if you really need to call a Trio function from a
29-
worker thread? That's where :class:`BlockingTrioPortal` comes in: it's the
30-
rare Trio object whose methods can – in fact, must! – be called from
31-
another thread, and it allows you to call all those other functions.
32-
33-
There is one complication: it's possible for a single Python program to
34-
contain multiple calls to :func:`trio.run`, either in sequence – like in a
35-
test suite that calls :func:`trio.run` for each test – or simultaneously
36-
in different threads. So how do you control which :func:`trio.run` your
37-
portal opens into?
38-
39-
The answer is that each :class:`BlockingTrioPortal` object is associated
40-
with one *specific* call to :func:`trio.run`.
41-
42-
The simplest way to set this up is to instantiate the class with no
43-
arguments inside Trio; this automatically binds it to the context where
44-
you instantiate it::
45-
46-
async def some_function():
47-
portal = trio.BlockingTrioPortal()
48-
await trio.run_sync_in_thread(sync_fn, portal)
49-
50-
Alternatively, you can pass an explicit :class:`trio.hazmat.TrioToken` to
51-
specify the :func:`trio.run` that you want your portal to connect to.
52-
53-
"""
54-
5524
def __init__(self, trio_token=None):
5625
if trio_token is None:
5726
trio_token = trio.hazmat.current_trio_token()
5827
self._trio_token = trio_token
5928

60-
# This is the part that runs in the Trio thread
61-
def _run_cb(self, q, afn, args):
62-
@disable_ki_protection
63-
async def unprotected_afn():
64-
return await afn(*args)
65-
66-
async def await_in_trio_thread_task():
67-
q.put_nowait(await outcome.acapture(unprotected_afn))
68-
69-
trio.hazmat.spawn_system_task(await_in_trio_thread_task, name=afn)
70-
71-
# This is the part that runs in the Trio thread
72-
def _run_sync_cb(self, q, fn, args):
73-
@disable_ki_protection
74-
def unprotected_fn():
75-
return fn(*args)
76-
77-
res = outcome.capture(unprotected_fn)
78-
q.put_nowait(res)
79-
80-
def _do_it(self, cb, fn, *args):
81-
try:
82-
trio.hazmat.current_task()
83-
except RuntimeError:
84-
pass
85-
else:
86-
raise RuntimeError(
87-
"this is a blocking function; call it from a thread"
88-
)
89-
q = stdlib_queue.Queue()
90-
self._trio_token.run_sync_soon(cb, q, fn, args)
91-
return q.get().unwrap()
92-
9329
def run(self, afn, *args):
94-
"""Run the given async function in the Trio thread, blocking until it
95-
is complete.
96-
97-
Returns or raises whatever the given function returns or raises. It
98-
can also raise exceptions of its own:
99-
100-
Raises:
101-
RunFinishedError: if the corresponding call to :func:`trio.run` has
102-
already completed.
103-
Cancelled: if the corresponding call to :func:`trio.run` completes
104-
while ``afn(*args)`` is running, then ``afn`` is likely to raise
105-
:class:`Cancelled`, and this will propagate out into
106-
RuntimeError: if you try calling this from inside the Trio thread,
107-
which would otherwise cause a deadlock.
108-
109-
"""
110-
return self._do_it(self._run_cb, afn, *args)
30+
return run(afn, *args, trio_token=self._trio_token)
11131

11232
def run_sync(self, fn, *args):
113-
"""Run the given synchronous function in the Trio thread, blocking
114-
until it is complete.
115-
116-
Returns or raises whatever the given function returns or raises. It
117-
can also exceptions of its own:
118-
119-
Raises:
120-
RunFinishedError: if the corresponding call to :func:`trio.run` has
121-
already completed.
122-
RuntimeError: if you try calling this from inside the Trio thread,
123-
which would otherwise cause a deadlock.
124-
125-
"""
126-
return self._do_it(self._run_sync_cb, fn, *args)
33+
return run_sync(fn, *args, trio_token=self._trio_token)
12734

12835

12936
################################################################
@@ -469,13 +376,13 @@ def run(afn, *args, trio_token=None):
469376
This happens when it was not spawned from trio.run_sync_in_thread.
470377
471378
**Locating a Trio Token**: There are two ways to specify which
472-
:func: ``trio.run()`` loop to reenter::
379+
`trio.run()` loop to reenter::
473380
474-
- Spawn this thread from :func: ``run_sync_in_thread``. This will
381+
- Spawn this thread from `run_sync_in_thread`. This will
475382
"inject" the current Trio Token into thread local storage and allow
476-
this function to re-enter the same :func: ``trio.run()`` loop.
383+
this function to re-enter the same `trio.run` loop.
477384
- Pass a keyword argument, ``trio_token`` specifiying a specific
478-
:func: ``trio.run()`` loop to re-enter. This is the "legacy" way of
385+
`trio.run` loop to re-enter. This is the "legacy" way of
479386
re-entering a trio thread and is similar to the old
480387
`BlockingTrioPortal`.
481388
"""
@@ -504,9 +411,9 @@ def run_sync(fn, *args, trio_token=None):
504411
can also raise exceptions of its own:
505412
506413
Raises:
507-
RunFinishedError: if the corresponding call to :func:`trio.run` has
414+
RunFinishedError: if the corresponding call to `trio.run` has
508415
already completed.
509-
Cancelled: if the corresponding call to :func:`trio.run` completes
416+
Cancelled: if the corresponding call to `trio.run` completes
510417
while ``afn(*args)`` is running, then ``afn`` is likely to raise
511418
:class:`Cancelled`, and this will propagate out into
512419
RuntimeError: if you try calling this from inside the Trio thread,
@@ -515,13 +422,13 @@ def run_sync(fn, *args, trio_token=None):
515422
This happens when it was not spawned from trio.run_sync_in_thread.
516423
517424
**Locating a Trio Token**: There are two ways to specify which
518-
:func: ``trio.run()`` loop to reenter::
425+
`trio.run` loop to reenter::
519426
520-
- Spawn this thread from :func: ``run_sync_in_thread``. This will
427+
- Spawn this thread from `run_sync_in_thread`. This will
521428
"inject" the current Trio Token into thread local storage and allow
522-
this function to re-enter the same :func: ``trio.run()`` loop.
429+
this function to re-enter the same `trio.run` loop.
523430
- Pass a keyword argument, ``trio_token`` specifiying a specific
524-
:func: ``trio.run()`` loop to re-enter. This is the "legacy" way of
431+
`trio.run()` loop to re-enter. This is the "legacy" way of
525432
re-entering a trio thread and is similar to the old
526433
`BlockingTrioPortal`.
527434
"""

0 commit comments

Comments
 (0)