Write the big boi docs content tree
Replace the ancient `docs/index.rst` (still teaching
`tractor.run()`, `@stream` + arbiters..) with a full ~32 page tree
teaching ONLY the current api (`.wait_for_result()`, registrar
naming, `@context` + `open_context()` as the core model),
- landing: hero example, feature cards + canon links,
- `start/`: install + a 4-example quickstart on-ramp,
- `explain/`: an "SC across processes" essay distilling the essence
per #157's orig ask + a runtime architecture tour,
- `guide/`: 12 task-focused pages incl the flagship multi-process
debugging walkthrough, `Context` + `MsgStream` deep-dives,
cancellation semantics (self-vs-cross cancel rules), discovery,
infected `asyncio`, typed msging + the #126 testing-tips page,
- `api/`: 10 curated autodoc pages (all targets import-verified vs
the reorg'd subpkg tree),
- `project/`: changelog include, ported dev-tips (drops old
`docs/dev_tips.rst`) + roadmap.
Every code block is a `literalinclude` from `examples/`
- zero duplication, all CI-run - w/ `d2` figs floated
into the RHS margin per the 3-col design. Build is green; the 24
remaining warnings all source from lib docstring rst-isms or legacy
`NEWS.rst` content.
Substantially resolves #157 (refine round pending); chips at #175 +
#126.
Prompt-IO: ai/prompt-io/claude/20260611T175152Z_8526985c_prompt_io.md
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-11 19:17:14 +00:00
|
|
|
Infected ``asyncio``
|
|
|
|
|
====================
|
|
|
|
|
``tractor`` is "just trio_", but the Python world is packed with
|
|
|
|
|
libraries that only speak ``asyncio``: websocket stacks, vendor
|
|
|
|
|
SDKs, that one exchange client you can't route around. Rather than
|
|
|
|
|
make you rewrite them, ``tractor`` lets you *quarantine* them inside
|
|
|
|
|
a dedicated subactor which runs both event loops at once, with full
|
|
|
|
|
`structured concurrency`_ (SC) guarantees maintained across the
|
|
|
|
|
loop boundary *and* the process tree.
|
|
|
|
|
|
|
|
|
|
In the project's own words:
|
|
|
|
|
|
|
|
|
|
Yes, we spawn a python process, run ``asyncio``, start ``trio``
|
|
|
|
|
on the ``asyncio`` loop, then send commands to the ``trio``
|
|
|
|
|
scheduled tasks to tell ``asyncio`` tasks what to do XD
|
|
|
|
|
|
|
|
|
|
We call this "infected ``asyncio``" mode: the subactor's stdlib
|
|
|
|
|
loop runs as the *host* with ``trio`` embedded on top in `guest`_
|
|
|
|
|
mode, and your ``trio`` tasks drive ``asyncio`` tasks through
|
|
|
|
|
a linked, SC-supervised, in-memory channel.
|
|
|
|
|
|
|
|
|
|
.. d2:: diagrams/infected_aio.d2
|
|
|
|
|
:caption: One process, two schedulers: ``trio`` rides the
|
|
|
|
|
``asyncio`` loop as a guest while the parent speaks plain
|
|
|
|
|
``tractor`` IPC, none the wiser.
|
|
|
|
|
:alt: parent actor connected over IPC to a subactor whose
|
|
|
|
|
asyncio loop hosts trio in guest mode, with a
|
|
|
|
|
LinkedTaskChannel pairing a trio task to an asyncio task
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
Infected ``asyncio`` mode is **experimental**: it works (we
|
|
|
|
|
beat on it plenty) but parts of the API surface and some
|
|
|
|
|
edge-case semantics are still settling. Got opinions on the
|
|
|
|
|
interop design? Feel free to sling them in `#273`_!
|
|
|
|
|
|
|
|
|
|
How the infection takes hold
|
|
|
|
|
----------------------------
|
|
|
|
|
A normal subactor boots by running the ``tractor`` runtime's task
|
|
|
|
|
tree directly under ``trio.run()``. Pass ``infect_asyncio=True``
|
|
|
|
|
at spawn time and the child's entrypoint changes shape entirely:
|
|
|
|
|
|
|
|
|
|
1. the process starts the stdlib loop via ``asyncio.run()``,
|
|
|
|
|
2. the first ``asyncio`` task calls
|
|
|
|
|
``trio.lowlevel.start_guest_run()``, embedding the ``trio``
|
|
|
|
|
scheduler *inside* the already running ``asyncio`` loop (the
|
|
|
|
|
upstream `guest`_-mode feature),
|
|
|
|
|
3. the regular ``tractor`` runtime then boots on the guest
|
|
|
|
|
``trio`` side and connects back to its parent like any other
|
|
|
|
|
subactor.
|
|
|
|
|
|
|
|
|
|
.. margin:: Symptoms
|
|
|
|
|
|
|
|
|
|
Looks like your stdlib event loop has caught a case of "the
|
|
|
|
|
trios"! Don't worry, you'll barely notice; and if anything
|
|
|
|
|
gets too bad, your parents will know about it B)
|
|
|
|
|
|
|
|
|
|
Both schedulers interleave in a single thread, no GIL gymnastics
|
|
|
|
|
required. From the rest of the actor tree the infected child is
|
|
|
|
|
indistinguishable from any other actor: same IPC protocol, same
|
|
|
|
|
supervision and cancellation semantics, same zombie-safety
|
|
|
|
|
guarantees. The difference is purely internal: ``trio`` tasks in
|
|
|
|
|
that process can start and drive ``asyncio`` tasks through the
|
|
|
|
|
``tractor.to_asyncio`` API.
|
|
|
|
|
|
|
|
|
|
Spawning an infected subactor
|
|
|
|
|
-----------------------------
|
|
|
|
|
Just flip the flag on :meth:`tractor.ActorNursery.start_actor`:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
async with tractor.open_nursery() as an:
|
|
|
|
|
portal = await an.start_actor(
|
|
|
|
|
'aio_side',
|
|
|
|
|
enable_modules=[__name__],
|
|
|
|
|
infect_asyncio=True,
|
|
|
|
|
)
|
|
|
|
|
|
Port docs off `run_in_actor` + `Portal.wait_for_result`
The 8-page docs sweep of the #477 removal, ahead of the API's
excision,
- `start/quickstart.rst`: the first-actor-tree walkthrough now
narrates the (migrated) `to_actor.run()` example — no portal
in hand until the daemon section introduces `start_actor()`.
- `guide/spawning.rst`: the one-shot section becomes
`to_actor.run()` (blocking call, placement opts, "built on the
primitives" note); lifetime/teardown rules update — one-shots
never make it to nursery exit since each is reaped inside its
own call.
- `guide/rpc.rst`: the `wait_for_result()` section (an API that
dies with the reap cluster, incl. the `NoResult` sentinel)
becomes a `to_actor.run()` one-shot section.
- `api/core.rst`: drop `run_in_actor`/`wait_for_result` from the
autodoc member lists, drop the `Portal.result()` deprecation
note, add a "One-shot task actors" `tractor.to_actor.run`
autodoc section.
- `guide/{asyncio,context,cancellation,parallelism}.rst`:
mention swaps to the successor API.
Gate: `make -C docs html` builds clean; `to_actor.run` autodoc
renders in `api/core.html`.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-07-06 16:31:02 +00:00
|
|
|
The one-shot convenience ``tractor.to_actor.run()`` accepts the
|
|
|
|
|
same flag. The ``to_asyncio`` APIs may **only** be called from
|
Write the big boi docs content tree
Replace the ancient `docs/index.rst` (still teaching
`tractor.run()`, `@stream` + arbiters..) with a full ~32 page tree
teaching ONLY the current api (`.wait_for_result()`, registrar
naming, `@context` + `open_context()` as the core model),
- landing: hero example, feature cards + canon links,
- `start/`: install + a 4-example quickstart on-ramp,
- `explain/`: an "SC across processes" essay distilling the essence
per #157's orig ask + a runtime architecture tour,
- `guide/`: 12 task-focused pages incl the flagship multi-process
debugging walkthrough, `Context` + `MsgStream` deep-dives,
cancellation semantics (self-vs-cross cancel rules), discovery,
infected `asyncio`, typed msging + the #126 testing-tips page,
- `api/`: 10 curated autodoc pages (all targets import-verified vs
the reorg'd subpkg tree),
- `project/`: changelog include, ported dev-tips (drops old
`docs/dev_tips.rst`) + roadmap.
Every code block is a `literalinclude` from `examples/`
- zero duplication, all CI-run - w/ `d2` figs floated
into the RHS margin per the 3-col design. Build is green; the 24
remaining warnings all source from lib docstring rst-isms or legacy
`NEWS.rst` content.
Substantially resolves #157 (refine round pending); chips at #175 +
#126.
Prompt-IO: ai/prompt-io/claude/20260611T175152Z_8526985c_prompt_io.md
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-11 19:17:14 +00:00
|
|
|
tasks inside an infected actor; calling them anywhere else raises
|
|
|
|
|
a loud ``RuntimeError``. You can introspect at runtime with
|
|
|
|
|
``tractor.current_actor().is_infected_aio()``.
|
|
|
|
|
|
|
|
|
|
Linking tasks with ``open_channel_from()``
|
|
|
|
|
------------------------------------------
|
|
|
|
|
The core primitive is :func:`tractor.to_asyncio.open_channel_from`,
|
|
|
|
|
an async context manager which starts your ``asyncio`` function as
|
|
|
|
|
a real ``asyncio.Task`` and yields a two-way channel linking it to
|
|
|
|
|
the calling ``trio`` task:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
from tractor import to_asyncio
|
|
|
|
|
|
|
|
|
|
async with to_asyncio.open_channel_from(
|
|
|
|
|
aio_main, # async def aio_main(chan, **kwargs)
|
|
|
|
|
period=0.5, # extra kwargs are passed through
|
|
|
|
|
) as (chan, first):
|
|
|
|
|
await chan.send('tick')
|
|
|
|
|
|
|
|
|
|
The semantics deliberately mirror the inter-actor ``Context``
|
|
|
|
|
handshake from :doc:`/guide/context`:
|
|
|
|
|
|
|
|
|
|
- the target fn must declare a parameter literally named ``chan``;
|
|
|
|
|
the runtime injects the shared
|
|
|
|
|
:class:`~tractor.to_asyncio.LinkedTaskChannel` by keyword.
|
|
|
|
|
- the ``trio`` side blocks at entry until the ``asyncio`` task
|
|
|
|
|
calls ``chan.started_nowait(value)``; that value is delivered as
|
|
|
|
|
``first``, exactly like the ``(ctx, first)`` pair you get from
|
|
|
|
|
``Portal.open_context()`` after the child calls
|
|
|
|
|
``ctx.started()``.
|
|
|
|
|
- a first value **must** be sent from the ``asyncio`` side or the
|
|
|
|
|
``trio`` side will never unblock.
|
|
|
|
|
- on block exit the pair is torn down *together*; neither task can
|
|
|
|
|
outlive the other (more on this below).
|
|
|
|
|
|
|
|
|
|
A full example: the echo server
|
|
|
|
|
-------------------------------
|
|
|
|
|
Here's the canonical demo, a round-trip echo service where the
|
|
|
|
|
``asyncio`` task is told what to do by a ``trio`` task which is in
|
|
|
|
|
turn driven over IPC by the root actor:
|
|
|
|
|
|
|
|
|
|
.. literalinclude:: ../../examples/infected_asyncio_echo_server.py
|
|
|
|
|
:caption: examples/infected_asyncio_echo_server.py
|
|
|
|
|
:language: python
|
|
|
|
|
|
|
|
|
|
What's going on?
|
|
|
|
|
|
|
|
|
|
- there are three task layers: the root actor's pure ``trio``
|
|
|
|
|
task, the infected child's ``trio``-side ``@tractor.context``
|
|
|
|
|
endpoint (``trio_to_aio_echo_server()``), and the child's
|
|
|
|
|
``asyncio`` task (``aio_echo_server()``).
|
|
|
|
|
- two ``started``-style handshakes compose: the aio task's
|
|
|
|
|
``chan.started_nowait('start')`` unblocks the child's
|
|
|
|
|
``open_channel_from()`` entry, then the child relays that same
|
|
|
|
|
value up via ``await ctx.started(first)`` which unblocks the
|
|
|
|
|
root's ``open_context()`` entry. Synchronization all the way
|
|
|
|
|
down, er, up.
|
|
|
|
|
- each round trip flows: root ``stream.send()`` -> IPC -> child
|
|
|
|
|
``async for msg in stream`` -> ``chan.send(msg)`` -> aio
|
|
|
|
|
``await chan.get()`` -> ``chan.send_nowait()`` -> child
|
|
|
|
|
``chan.receive()`` -> ``stream.send(out)`` -> IPC -> root.
|
|
|
|
|
- when the root breaks out of its stream loop and exits the
|
|
|
|
|
context block, the child's stream ends, its channel block exits,
|
|
|
|
|
and the ``asyncio`` task is reaped along with it; the final
|
|
|
|
|
``portal.cancel_actor()`` then tears down the whole process. No
|
|
|
|
|
orphaned ``asyncio`` tasks, no zombie procs; if you manage to
|
|
|
|
|
create either it **is a bug**.
|
|
|
|
|
|
|
|
|
|
``LinkedTaskChannel``: one channel, two sides
|
|
|
|
|
---------------------------------------------
|
|
|
|
|
The same channel object is shared by both tasks; which methods you
|
|
|
|
|
call depends on which loop schedules your task. The ``trio`` side
|
|
|
|
|
gets a standard ``trio.abc.Channel`` interface while the
|
|
|
|
|
``asyncio`` side gets queue-flavored, mostly-sync methods:
|
|
|
|
|
|
|
|
|
|
.. list-table::
|
|
|
|
|
:header-rows: 1
|
|
|
|
|
:widths: 14 36 50
|
|
|
|
|
|
|
|
|
|
* - side
|
|
|
|
|
- call
|
|
|
|
|
- what it does
|
|
|
|
|
* - ``trio``
|
|
|
|
|
- ``await chan.send(item)``
|
|
|
|
|
- ship ``item`` to the ``asyncio`` task (enqueues onto an
|
|
|
|
|
internal ``asyncio.Queue``).
|
|
|
|
|
* - ``trio``
|
|
|
|
|
- ``await chan.receive()``
|
|
|
|
|
- wait for the next value from the ``asyncio`` side; the
|
|
|
|
|
channel also supports ``async for``.
|
|
|
|
|
* - ``trio``
|
|
|
|
|
- ``await chan.wait_for_result()``
|
|
|
|
|
- block until the ``asyncio`` task completes; return its
|
|
|
|
|
final result or raise its (translated) error.
|
|
|
|
|
* - ``trio``
|
|
|
|
|
- ``chan.subscribe()``
|
|
|
|
|
- acm yielding a ``BroadcastReceiver`` so N local tasks can
|
|
|
|
|
each consume a copy of the inbound stream (see below).
|
|
|
|
|
* - ``trio``
|
|
|
|
|
- ``chan.cancel_asyncio_task()``
|
|
|
|
|
- explicitly request cancellation of the linked ``asyncio``
|
|
|
|
|
task.
|
|
|
|
|
* - ``asyncio``
|
|
|
|
|
- ``chan.started_nowait(value)``
|
|
|
|
|
- deliver the "first" value; unblocks the ``trio`` side's
|
|
|
|
|
``open_channel_from()`` entry (mirrors ``ctx.started()``).
|
|
|
|
|
* - ``asyncio``
|
|
|
|
|
- ``await chan.get()``
|
|
|
|
|
- wait for the next value sent from the ``trio`` side.
|
|
|
|
|
* - ``asyncio``
|
|
|
|
|
- ``chan.send_nowait(item)``
|
|
|
|
|
- push a value to the ``trio`` side without blocking.
|
|
|
|
|
|
|
|
|
|
Fan-out with ``.subscribe()``
|
|
|
|
|
*****************************
|
|
|
|
|
Just like :meth:`tractor.MsgStream.subscribe` does for IPC
|
|
|
|
|
streams, ``chan.subscribe()`` lets multiple local ``trio`` tasks
|
|
|
|
|
each receive *every* value sent from the single ``asyncio`` task:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
async with chan.subscribe() as bcast:
|
|
|
|
|
async for msg in bcast:
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
The underlying broadcast machinery is lazily allocated on first
|
|
|
|
|
use and is *not* reversible for the channel's remaining lifetime,
|
|
|
|
|
so only reach for it when you actually want the fan-out.
|
|
|
|
|
|
|
|
|
|
One-shot calls with ``run_task()``
|
|
|
|
|
----------------------------------
|
|
|
|
|
When you just want a single ``asyncio`` result and no streaming
|
|
|
|
|
dialog, skip the channel ceremony and use
|
|
|
|
|
:func:`tractor.to_asyncio.run_task`:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
from tractor import to_asyncio
|
|
|
|
|
|
|
|
|
|
async def aio_fetch(url: str) -> str:
|
|
|
|
|
await asyncio.sleep(0.3) # pretend-IO, aio style
|
|
|
|
|
return f'<html>sup {url}</html>'
|
|
|
|
|
|
|
|
|
|
# from any trio task inside the infected actor:
|
|
|
|
|
page = await to_asyncio.run_task(aio_fetch, url='https://x.io')
|
|
|
|
|
|
|
|
|
|
It schedules the fn as an ``asyncio.Task``, waits for completion
|
|
|
|
|
and hands the return value back to ``trio``; think of it as the
|
Port docs off `run_in_actor` + `Portal.wait_for_result`
The 8-page docs sweep of the #477 removal, ahead of the API's
excision,
- `start/quickstart.rst`: the first-actor-tree walkthrough now
narrates the (migrated) `to_actor.run()` example — no portal
in hand until the daemon section introduces `start_actor()`.
- `guide/spawning.rst`: the one-shot section becomes
`to_actor.run()` (blocking call, placement opts, "built on the
primitives" note); lifetime/teardown rules update — one-shots
never make it to nursery exit since each is reaped inside its
own call.
- `guide/rpc.rst`: the `wait_for_result()` section (an API that
dies with the reap cluster, incl. the `NoResult` sentinel)
becomes a `to_actor.run()` one-shot section.
- `api/core.rst`: drop `run_in_actor`/`wait_for_result` from the
autodoc member lists, drop the `Portal.result()` deprecation
note, add a "One-shot task actors" `tractor.to_actor.run`
autodoc section.
- `guide/{asyncio,context,cancellation,parallelism}.rst`:
mention swaps to the successor API.
Gate: `make -C docs html` builds clean; `to_actor.run` autodoc
renders in `api/core.html`.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-07-06 16:31:02 +00:00
|
|
|
cross-loop sibling of ``tractor.to_actor.run()``. Errors and
|
Write the big boi docs content tree
Replace the ancient `docs/index.rst` (still teaching
`tractor.run()`, `@stream` + arbiters..) with a full ~32 page tree
teaching ONLY the current api (`.wait_for_result()`, registrar
naming, `@context` + `open_context()` as the core model),
- landing: hero example, feature cards + canon links,
- `start/`: install + a 4-example quickstart on-ramp,
- `explain/`: an "SC across processes" essay distilling the essence
per #157's orig ask + a runtime architecture tour,
- `guide/`: 12 task-focused pages incl the flagship multi-process
debugging walkthrough, `Context` + `MsgStream` deep-dives,
cancellation semantics (self-vs-cross cancel rules), discovery,
infected `asyncio`, typed msging + the #126 testing-tips page,
- `api/`: 10 curated autodoc pages (all targets import-verified vs
the reorg'd subpkg tree),
- `project/`: changelog include, ported dev-tips (drops old
`docs/dev_tips.rst`) + roadmap.
Every code block is a `literalinclude` from `examples/`
- zero duplication, all CI-run - w/ `d2` figs floated
into the RHS margin per the 3-col design. Build is green; the 24
remaining warnings all source from lib docstring rst-isms or legacy
`NEWS.rst` content.
Substantially resolves #157 (refine round pending); chips at #175 +
#126.
Prompt-IO: ai/prompt-io/claude/20260611T175152Z_8526985c_prompt_io.md
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-11 19:17:14 +00:00
|
|
|
cancellation are translated exactly as for channels.
|
|
|
|
|
|
|
|
|
|
Cross-loop errors and cancellation
|
|
|
|
|
----------------------------------
|
|
|
|
|
The paired tasks are *SC linked*: exception and cancel handling
|
|
|
|
|
tears down **both** sides on any unexpected error or cancellation,
|
|
|
|
|
in either loop. There is no fire-and-forget mode; a
|
|
|
|
|
``LinkedTaskChannel`` is a supervision scope just like a
|
|
|
|
|
``Context`` is across processes.
|
|
|
|
|
|
|
|
|
|
Because each loop has its own (incompatible) cancellation and exit
|
|
|
|
|
machinery, boundary crossings are translated into dedicated
|
|
|
|
|
exception types, all importable from ``tractor.to_asyncio``:
|
|
|
|
|
|
|
|
|
|
.. list-table::
|
|
|
|
|
:header-rows: 1
|
|
|
|
|
:widths: 26 22 52
|
|
|
|
|
|
|
|
|
|
* - exception
|
|
|
|
|
- raised in
|
|
|
|
|
- meaning
|
|
|
|
|
* - ``AsyncioCancelled``
|
|
|
|
|
- the ``trio`` task
|
|
|
|
|
- the linked ``asyncio`` task was cancelled by itself or
|
|
|
|
|
a 3rd party (i.e. *not* by the ``trio`` side).
|
|
|
|
|
* - ``AsyncioTaskExited``
|
|
|
|
|
- the ``trio`` task
|
|
|
|
|
- the ``asyncio`` task returned/exited early while the
|
|
|
|
|
``trio`` side still held the link open.
|
|
|
|
|
* - ``TrioCancelled``
|
|
|
|
|
- the ``asyncio`` task
|
|
|
|
|
- the ``trio`` side was cancelled (or crashed) so the
|
|
|
|
|
``asyncio`` task is being torn down per SC rules.
|
|
|
|
|
* - ``TrioTaskExited``
|
|
|
|
|
- the ``asyncio`` task
|
|
|
|
|
- the ``trio`` side exited gracefully while the ``asyncio``
|
|
|
|
|
task was still running; a "clean shutdown" signal much
|
|
|
|
|
like closing a ``trio`` mem-chan.
|
|
|
|
|
|
|
|
|
|
By default ``open_channel_from(suppress_graceful_exits=True)``
|
|
|
|
|
absorbs the two ``*TaskExited`` signals so happy-path teardown
|
|
|
|
|
stays silent; pass ``False`` when your app wants to handle early
|
|
|
|
|
peer-exit explicitly.
|
|
|
|
|
|
|
|
|
|
Past the task pair, everything composes with the normal actor
|
|
|
|
|
story: an unhandled ``asyncio`` error is translated into the
|
|
|
|
|
``trio`` side, propagates out of your ``@tractor.context``
|
|
|
|
|
endpoint, and arrives at the parent boxed as
|
|
|
|
|
a :class:`tractor.RemoteActorError`. One SC discipline,
|
|
|
|
|
end-to-end, across loops *and* processes.
|
|
|
|
|
|
|
|
|
|
Breakpoints in ``asyncio`` tasks
|
|
|
|
|
--------------------------------
|
|
|
|
|
Yes, the multi-actor REPL works here too. With
|
|
|
|
|
``debug_mode=True`` enabled on your tree the ``trio`` side of an
|
|
|
|
|
infected actor can ``await tractor.pause()`` as usual, and with
|
|
|
|
|
greenback enabled (``maybe_enable_greenback=True``) even the
|
|
|
|
|
builtin ``breakpoint()`` works from *inside* ``asyncio`` tasks;
|
|
|
|
|
see ``examples/debugging/asyncio_bp.py`` for the full tour. The
|
|
|
|
|
root-TTY locking dance behind all this is covered in
|
|
|
|
|
:doc:`/guide/debugging`.
|
|
|
|
|
|
|
|
|
|
Where to next?
|
|
|
|
|
--------------
|
|
|
|
|
.. seealso::
|
|
|
|
|
|
|
|
|
|
- :doc:`/guide/context` for the inter-actor handshake and
|
|
|
|
|
streaming APIs which this whole interop layer mirrors.
|
|
|
|
|
- :doc:`/guide/msging` for typing the payloads you shuttle
|
|
|
|
|
between actors (and loops).
|
|
|
|
|
- :doc:`/guide/debugging` for the multi-process REPL that
|
|
|
|
|
keeps working even when your loop has "the trios".
|
|
|
|
|
|
|
|
|
|
.. _trio: https://github.com/python-trio/trio
|
|
|
|
|
.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
|
|
|
|
|
.. _guest: https://trio.readthedocs.io/en/stable/reference-lowlevel.html?highlight=guest%20mode#using-guest-mode-to-run-trio-on-top-of-other-event-loops
|
|
|
|
|
.. _#273: https://github.com/goodboy/tractor/issues/273
|