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
|
|
|
The ``Context``: a cross-actor task pair
|
|
|
|
|
=========================================
|
|
|
|
|
|
|
|
|
|
If you've written any trio_ you already know the contract: every
|
|
|
|
|
task lives in a nursery, errors always propagate, cancellation is
|
|
|
|
|
scoped, and nothing leaks. ``tractor`` extends that exact contract
|
|
|
|
|
*across processes* — the same guarantees from the seminal
|
|
|
|
|
`blog post`_, just with the nursery split across two memory
|
|
|
|
|
domains. The primitive that does it is :class:`tractor.Context`: a
|
|
|
|
|
**linked pair of tasks**, one in each of two actors, supervised as
|
|
|
|
|
a single `structured concurrency`_ (SC) scope over IPC.
|
|
|
|
|
|
|
|
|
|
.. d2:: diagrams/context_handshake.d2
|
|
|
|
|
:caption: The SC-transitive supervision protocol, msg by msg.
|
|
|
|
|
:alt: sequence diagram of the context handshake msg flow
|
|
|
|
|
|
|
|
|
|
Pretty much everything else is (or is slated to be) built on this
|
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
|
|
|
one primitive: ``tractor.to_actor.run()`` is a convenience for
|
|
|
|
|
"spawn, run the lone task, await the result, tear down"; plain
|
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
|
|
|
``Portal.run()`` RPC is planned to be re-implemented on top of it;
|
|
|
|
|
the multi-process debugger's tree-wide REPL lock rides one. Grok
|
|
|
|
|
this page and the rest of the library reads as convenience
|
|
|
|
|
wrappers B)
|
|
|
|
|
|
|
|
|
|
The endpoint contract
|
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
|
|
A context endpoint is an async function decorated with
|
|
|
|
|
:func:`tractor.context` which declares **a param annotated**
|
|
|
|
|
``tractor.Context`` — any param name you like, the annotation is
|
|
|
|
|
what's required:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
@tractor.context
|
|
|
|
|
async def trainer(
|
|
|
|
|
ctx: tractor.Context,
|
|
|
|
|
model: str,
|
|
|
|
|
) -> str:
|
|
|
|
|
await ctx.started('ready')
|
|
|
|
|
return f'trained {model}'
|
|
|
|
|
|
|
|
|
|
.. margin:: Who am I talking to?
|
|
|
|
|
|
|
|
|
|
Inside any context task
|
|
|
|
|
:func:`tractor.current_ipc_ctx` returns the
|
|
|
|
|
``Context`` bound to the current task; handy
|
|
|
|
|
in helpers that don't take ``ctx`` explicitly.
|
|
|
|
|
|
|
|
|
|
The parent (aka "opener") side invokes it through a
|
|
|
|
|
:class:`tractor.Portal` using ``Portal.open_context()``, passing
|
|
|
|
|
any extra kwargs which are shipped over the wire as the remote
|
|
|
|
|
task's arguments. Since the target fn is referenced by module
|
|
|
|
|
path, that module must be listed in the peer actor's
|
|
|
|
|
``enable_modules`` allowlist — RPC capability is always opt-in.
|
|
|
|
|
|
|
|
|
|
The decorator also accepts a ``pld_spec``: a type (union) which
|
|
|
|
|
every payload in the dialog is validated against, upgrading your
|
|
|
|
|
msgs to a typed contract enforced via :exc:`tractor.MsgTypeError`.
|
|
|
|
|
Validation strictness follows the "`cheap or nasty`_"
|
|
|
|
|
`(un)protocol`_ pattern: the one-shot ``Started`` payload gets the
|
|
|
|
|
nasty treatment (stringently round-trip checked before it's even
|
|
|
|
|
sent) while high-rate stream payloads stay cheap (checked only
|
|
|
|
|
receiver side).
|
|
|
|
|
|
|
|
|
|
The handshake, on the wire
|
|
|
|
|
--------------------------
|
|
|
|
|
|
|
|
|
|
Every context runs one instance of ``tractor``'s "SC-transitive
|
|
|
|
|
supervision protocol": a tiny fixed grammar of msgspec_-typed msgs
|
|
|
|
|
encapsulating *all* RPC dialogs between actors. *Transitive*
|
|
|
|
|
because each IPC link obeys the same rules a local nursery does —
|
|
|
|
|
starts are acked, completion is awaited, errors and cancels always
|
|
|
|
|
relay — so chaining links across a process tree composes into one
|
|
|
|
|
tree-wide SC scope.
|
|
|
|
|
|
|
|
|
|
The figure up top shows a full dialog; in order:
|
|
|
|
|
|
|
|
|
|
``Start``
|
|
|
|
|
sent by ``Portal.open_context()``: "schedule a task running
|
|
|
|
|
this function with these kwargs".
|
|
|
|
|
|
|
|
|
|
``StartAck``
|
|
|
|
|
the peer runtime confirms the task is scheduled and that the
|
|
|
|
|
endpoint really is a context-style fn.
|
|
|
|
|
|
|
|
|
|
``Started``
|
|
|
|
|
emitted when the child task calls
|
|
|
|
|
:meth:`tractor.Context.started`; carries the first payload
|
|
|
|
|
and unblocks the parent's entry of ``open_context()``.
|
|
|
|
|
|
|
|
|
|
``Yield``
|
|
|
|
|
one per :meth:`tractor.MsgStream.send`, flowing in *either*
|
|
|
|
|
direction while a stream is open.
|
|
|
|
|
|
|
|
|
|
``Stop``
|
|
|
|
|
graceful end-of-stream: the far side's ``async for``
|
|
|
|
|
terminates cleanly.
|
|
|
|
|
|
|
|
|
|
``Return``
|
|
|
|
|
the child fn returned; its value becomes the context's final
|
|
|
|
|
result. If the child raised instead, an ``Error`` msg takes
|
|
|
|
|
this slot carrying the boxed traceback.
|
|
|
|
|
|
|
|
|
|
``ctx.started()``: just like ``task_status.started()``
|
|
|
|
|
*******************************************************
|
|
|
|
|
|
|
|
|
|
The startup phase is a deliberate clone of
|
|
|
|
|
:meth:`trio.Nursery.start` semantics: the child decides when it's
|
|
|
|
|
"up", optionally handing back a first value, and the parent stays
|
|
|
|
|
blocked until that moment:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
# trio, in-process
|
|
|
|
|
first = await nursery.start(child_fn)
|
|
|
|
|
|
|
|
|
|
# tractor, cross-process
|
|
|
|
|
async with portal.open_context(child_fn) as (ctx, first):
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
The ``as (ctx, first)`` tuple is exactly that pair: the
|
|
|
|
|
:class:`tractor.Context` handle plus whatever value the child
|
|
|
|
|
passed to ``await ctx.started(value)``. And readiness is not
|
|
|
|
|
optional — for instance opening a stream before ``.started()``
|
|
|
|
|
has been called raises a ``RuntimeError``; handshake first, then
|
|
|
|
|
dialog.
|
|
|
|
|
|
|
|
|
|
Bidirectional streaming over a context
|
|
|
|
|
--------------------------------------
|
|
|
|
|
|
|
|
|
|
The canonical ping-pong (design history: `#53`_, `#223`_) — a
|
|
|
|
|
full-duplex msg stream between a parent and its spawned peer:
|
|
|
|
|
|
|
|
|
|
.. literalinclude:: ../../examples/rpc_bidir_streaming.py
|
|
|
|
|
:caption: examples/rpc_bidir_streaming.py
|
|
|
|
|
:language: python
|
|
|
|
|
|
|
|
|
|
What's going on?
|
|
|
|
|
|
|
|
|
|
- ``start_actor()`` spawns the daemon-style subactor
|
|
|
|
|
``'rpc_server'`` with this very module in its allowlist.
|
|
|
|
|
|
|
|
|
|
- ``portal.open_context(simple_rpc, data=10)`` fires the
|
|
|
|
|
``Start`` msg then blocks until the child task calls
|
|
|
|
|
``await ctx.started(data + 1)`` — hence ``sent == 11``.
|
|
|
|
|
|
|
|
|
|
- both tasks enter ``ctx.open_stream()``: a stream dialog is only
|
|
|
|
|
fully open once *each* side has entered its block.
|
|
|
|
|
|
|
|
|
|
- the parent seeds the first ``'ping'``; each side then echoes
|
|
|
|
|
the other, one ``Yield`` msg per ``stream.send()``.
|
|
|
|
|
|
|
|
|
|
- after the 9th pong the parent ``break``\ s (10 pings sent in
|
|
|
|
|
total) and exits its stream block, which sends ``Stop``; the
|
|
|
|
|
child's ``async for`` completes gracefully and its ``else``
|
|
|
|
|
clause asserts all 10 pings arrived.
|
|
|
|
|
|
|
|
|
|
- the 10th in-transit pong? Discarded by the implicit drain at
|
|
|
|
|
``open_context()`` exit, which runs the dialog down to the
|
|
|
|
|
child's ``Return`` (here ``None``).
|
|
|
|
|
|
|
|
|
|
- daemon actors live until told otherwise:
|
|
|
|
|
``portal.cancel_actor()`` reaps the subactor explicitly.
|
|
|
|
|
|
|
|
|
|
Results: the ``Return`` leg
|
|
|
|
|
---------------------------
|
|
|
|
|
|
|
|
|
|
Every context resolves to a final outcome. Wait on it explicitly
|
|
|
|
|
from the parent side:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
async with portal.open_context(ep) as (ctx, first):
|
|
|
|
|
...
|
|
|
|
|
result = await ctx.wait_for_result()
|
|
|
|
|
|
|
|
|
|
or just exit the block — ``__aexit__`` implicitly drains the msg
|
|
|
|
|
flow until the ``Return`` (or ``Error``) arrives, discarding any
|
|
|
|
|
in-transit ``Yield``\ s on the way. Either way the rule of
|
|
|
|
|
`causality`_ holds exactly as in a local nursery: **the opener
|
|
|
|
|
never unblocks before the remote task is done**.
|
|
|
|
|
|
|
|
|
|
For post-hoc inspection (think supervision/restart logic) the ctx
|
|
|
|
|
also exposes ``Context.outcome``, ``.maybe_error`` and
|
|
|
|
|
``.has_outcome`` — where a "result" might well be the error the
|
|
|
|
|
dialog ended with.
|
|
|
|
|
|
|
|
|
|
Cancellation semantics
|
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
|
|
The part you actually came for; read it twice B)
|
|
|
|
|
|
|
|
|
|
A context's two tasks are **cancel-scope-linked across the IPC
|
|
|
|
|
boundary**: whatever ends one side — error, cancellation, plain
|
|
|
|
|
old return — is relayed such that the other side ends
|
|
|
|
|
equivalently. No silent half-open dialogs, no orphaned remote
|
|
|
|
|
tasks, ever.
|
|
|
|
|
|
|
|
|
|
``ctx.cancel()`` cancels the *remote* task
|
|
|
|
|
*******************************************
|
|
|
|
|
|
|
|
|
|
:meth:`tractor.Context.cancel` requests cancellation of the
|
|
|
|
|
**remote** task only:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
async with portal.open_context(ep) as (ctx, first):
|
|
|
|
|
await accomplish_things(ctx)
|
|
|
|
|
await ctx.cancel() # remote task, NOT me
|
|
|
|
|
|
|
|
|
|
A :class:`tractor.Context` is **not** a :class:`trio.CancelScope`:
|
|
|
|
|
the call doesn't (and can't) cancel your local task. It sends the
|
|
|
|
|
cancel request and waits a bounded ``timeout`` for the peer
|
|
|
|
|
runtime's ``CancelAck``, then your code proceeds to the block exit
|
|
|
|
|
as normal.
|
|
|
|
|
|
|
|
|
|
Compare scopes here: ``Portal.cancel_actor()`` is the big hammer
|
|
|
|
|
which cancels the peer's **entire runtime** (and thus process);
|
|
|
|
|
``ctx.cancel()`` is the per-dialog scalpel.
|
|
|
|
|
|
|
|
|
|
``ContextCancelled`` and the absorption rule
|
|
|
|
|
*********************************************
|
|
|
|
|
|
|
|
|
|
When a context task gets cancelled *by request* the requestee's
|
|
|
|
|
runtime reports back with a :exc:`tractor.ContextCancelled`
|
|
|
|
|
("ctxc") whose ``.canceller`` field holds the uid of the actor
|
|
|
|
|
which asked. That one field decides what you observe:
|
|
|
|
|
|
|
|
|
|
**you requested it**
|
|
|
|
|
i.e. ``ctxc.canceller == tractor.current_actor().uid``: the
|
|
|
|
|
ctxc is **absorbed** at ``open_context()`` exit — nothing
|
|
|
|
|
raises in your block. You asked for a graceful stop and got
|
|
|
|
|
it; if you care, ``await ctx.wait_for_result()`` hands the
|
|
|
|
|
ctxc back as a plain *value* for inspection.
|
|
|
|
|
|
|
|
|
|
**anyone else requested it**
|
|
|
|
|
the peer cancelling itself, or some third actor cancelling it
|
|
|
|
|
from the side: the ctxc **is raised** in your block. From
|
|
|
|
|
your scope's perspective a task you depend on was killed out
|
|
|
|
|
from under you and SC demands you hear about it — exactly
|
|
|
|
|
like a sibling crash in a `nursery`_.
|
|
|
|
|
|
|
|
|
|
In code:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
async with portal.open_context(ep) as (ctx, first):
|
|
|
|
|
...
|
|
|
|
|
except tractor.ContextCancelled as ctxc:
|
|
|
|
|
# can only be a peer- or third-party cancel;
|
|
|
|
|
# self-requested cancels are absorbed at exit.
|
|
|
|
|
assert ctxc.canceller != tractor.current_actor().uid
|
|
|
|
|
|
|
|
|
|
This self- vs cross-cancel split is what makes explicit teardown
|
|
|
|
|
*composable*: a supervisor cancels its dialogs without try/except
|
|
|
|
|
noise, while unexpected cancellation anywhere in the tree still
|
|
|
|
|
propagates loudly like any other failure.
|
|
|
|
|
|
|
|
|
|
.. warning::
|
|
|
|
|
|
|
|
|
|
Once ``ctx.cancel()`` has been called the dialog is done: a
|
|
|
|
|
subsequent ``ctx.open_stream()`` raises ``RuntimeError``.
|
|
|
|
|
|
|
|
|
|
For introspection the ctx exposes trio-flavored status props:
|
|
|
|
|
``.cancel_called`` (this side requested), ``.cancel_acked`` (peer
|
|
|
|
|
confirmed), ``.cancelled_caught`` and ``.canceller`` —
|
|
|
|
|
deliberately mirroring :class:`trio.CancelScope` naming.
|
|
|
|
|
|
|
|
|
|
Errors propagate, both ways
|
|
|
|
|
---------------------------
|
|
|
|
|
|
|
|
|
|
A crash on either end tears down the pair, SC style:
|
|
|
|
|
|
|
|
|
|
- **child raises**: the exception ships back as an ``Error`` msg
|
|
|
|
|
and re-raises in the parent block boxed as a
|
|
|
|
|
:exc:`tractor.RemoteActorError`; the original class rides along
|
|
|
|
|
as ``.boxed_type`` with ``.src_uid`` naming the crashed actor.
|
|
|
|
|
|
|
|
|
|
- **parent raises** (or is cancelled) inside the block: an
|
|
|
|
|
equivalent error/cancel is relayed to the child task so it can
|
|
|
|
|
never outlive the dialog.
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
async with portal.open_context(ep) as (ctx, first):
|
|
|
|
|
...
|
|
|
|
|
except tractor.RemoteActorError as rae:
|
|
|
|
|
if rae.boxed_type is ValueError:
|
|
|
|
|
... # remote ValueError, type preserved
|
|
|
|
|
|
|
|
|
|
Errors that hop through intermediary actors on their way up the
|
|
|
|
|
tree ("inceptions" XD) keep the full relay trail in
|
|
|
|
|
``.relay_uid`` / ``.relay_path``. Payloads violating your declared
|
|
|
|
|
``pld_spec`` surface as the IPC analog of a ``TypeError``:
|
|
|
|
|
:exc:`tractor.MsgTypeError`.
|
|
|
|
|
|
|
|
|
|
Overruns and backpressure
|
|
|
|
|
-------------------------
|
|
|
|
|
|
|
|
|
|
Stream msgs land in a bounded per-context buffer on the receiver
|
|
|
|
|
side. A sender that outpaces a non-consuming receiver *overruns*
|
Fix review nits from PR #460 self-review
Address the actionable findings from the `/code-review` pass
(#1-6); the d2-ext + docstring nits (#7-10) are left for a
follow-up.
Deats,
- `uds_transport_actor_tree.py`: `portal.chan.raddr.sockpath` is
the *shared listener* socket (named for the root registrar), NOT
the child's path — relabel it + lead with the per-child peer pid,
and stop claiming it's the child addr in the docstring,
- `docs.yml`: scope the `pages` `concurrency` group to the `deploy`
job w/ `cancel-in-progress: false` so a PR build can't cancel an
in-flight production deploy,
- `architecture.rst`: `'subint'` is not a selectable `start_method`
on this branch (`SpawnMethodKey` lacks it) — reframe as
in-development/roadmap,
- `context.rst`: `StreamOverrun` isn't re-exported from `tractor`;
point at `tractor._exceptions`,
- `debugging/`: sweep the 3 literalinclude'd examples off the
deprecated `.result()` -> `.wait_for_result()`,
- `quickstart.rst`: proc-title is `name@pid` (per `Aid.reprol`),
not `@uuid`.
NOTE, the `debugging/` examples are pexpect-tested; re-run
`tests/devx/test_debugger.py` for them.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-25 21:07:44 +00:00
|
|
|
it and the runtime raises ``StreamOverrun`` (from
|
|
|
|
|
``tractor._exceptions``; also a :exc:`trio.TooSlowError`) instead
|
|
|
|
|
of buffering without bound — SC discipline applies to memory too.
|
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
|
|
|
|
|
|
|
|
Your knobs:
|
|
|
|
|
|
|
|
|
|
- ``msg_buffer_size`` on ``ctx.open_stream()`` sizes the buffer.
|
|
|
|
|
|
|
|
|
|
- ``allow_overruns=True`` (on ``Portal.open_context()`` and/or
|
|
|
|
|
``ctx.open_stream()``) opts in to absorbing overflow instead of
|
|
|
|
|
erroring — reasonable for bursty telemetry-ish feeds, just know
|
|
|
|
|
you're trading the error for extra buffering.
|
|
|
|
|
|
|
|
|
|
One context, one stream
|
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
|
|
A ``MsgStream`` is strictly **one-shot use**: once it closes —
|
|
|
|
|
gracefully or not, from either side — it can never be re-opened
|
|
|
|
|
on the same ctx. Want another round with the same peer? Open a
|
|
|
|
|
fresh context; they're cheap. The full close-vs-cancel teardown
|
|
|
|
|
story lives in :doc:`/guide/streaming`.
|
|
|
|
|
|
|
|
|
|
.. rubric:: Where to next?
|
|
|
|
|
|
|
|
|
|
:doc:`/guide/streaming` covers the rest of the msg-moving story:
|
|
|
|
|
the legacy one-way API, multi-actor pipelines and in-actor
|
|
|
|
|
broadcast fan-out. For exhaustive API detail see
|
|
|
|
|
:class:`tractor.Context`, :class:`tractor.MsgStream` and
|
|
|
|
|
:exc:`tractor.ContextCancelled`.
|
|
|
|
|
|
|
|
|
|
.. _trio: https://github.com/python-trio/trio
|
|
|
|
|
.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
|
|
|
|
|
.. _blog post: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
|
|
|
|
|
.. _nursery: https://trio.readthedocs.io/en/latest/reference-core.html#nurseries-and-spawning
|
|
|
|
|
.. _causality: https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#c-c-c-c-causality-breaker
|
|
|
|
|
.. _cheap or nasty: https://zguide.zeromq.org/docs/chapter7/#The-Cheap-or-Nasty-Pattern
|
|
|
|
|
.. _(un)protocol: https://zguide.zeromq.org/docs/chapter7/#Unprotocols
|
|
|
|
|
.. _msgspec: https://jcristharif.com/msgspec/
|
|
|
|
|
.. _#53: https://github.com/goodboy/tractor/issues/53
|
|
|
|
|
.. _#223: https://github.com/goodboy/tractor/issues/223
|