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
|
|
|
Anatomy of the runtime
|
|
|
|
|
======================
|
|
|
|
|
|
|
|
|
|
You can get a long way with ``tractor`` by treating it as "trio_
|
|
|
|
|
with nurseries that spawn processes". But once you start asking
|
|
|
|
|
*where does my msg actually go?*, *which process is that?* or
|
|
|
|
|
*who keeps the phonebook?*, it pays to know how the runtime hangs
|
|
|
|
|
together. This page walks the stack top to bottom.
|
|
|
|
|
|
|
|
|
|
.. d2:: diagrams/runtime_stack.d2
|
|
|
|
|
:caption: The four runtime layers inside *every* actor process.
|
|
|
|
|
:alt: layer cake of app tasks, tractor runtime, IPC, OS process
|
|
|
|
|
:width: 70%
|
|
|
|
|
|
|
|
|
|
The layer cake
|
|
|
|
|
--------------
|
|
|
|
|
|
|
|
|
|
Every actor process is the same four-layer sandwich:
|
|
|
|
|
|
|
|
|
|
- **your app**: plain ``trio`` tasks, nurseries and cancel
|
|
|
|
|
scopes; nothing special. ``tractor`` is a `structured
|
|
|
|
|
concurrency`_ (SC) multi-processing runtime built on trio_ and
|
|
|
|
|
the whole pitch is that this layer stays *just trio*: no
|
|
|
|
|
callbacks, no futures, no proxy objects.
|
|
|
|
|
- **the** ``tractor`` **runtime**: a per-process
|
|
|
|
|
:class:`tractor.Actor` running the msg loop and RPC task
|
|
|
|
|
scheduler, plus the user-facing primitives layered on it:
|
|
|
|
|
:class:`tractor.ActorNursery` (spawning + supervision),
|
|
|
|
|
:class:`tractor.Portal` (calling into a peer) and
|
|
|
|
|
:class:`tractor.Context` + :class:`tractor.MsgStream`
|
|
|
|
|
(SC-linked cross-actor task pairs and streaming).
|
|
|
|
|
- **IPC channels**: one :class:`tractor.Channel` per connected
|
|
|
|
|
peer, each wrapping a ``MsgTransport`` that ships
|
|
|
|
|
msgspec_-typed msgs over TCP or UDS.
|
|
|
|
|
- **the OS**: one process per actor, started by a swappable
|
|
|
|
|
spawn backend.
|
|
|
|
|
|
|
|
|
|
The property that holds it all together: SC composes *through*
|
|
|
|
|
the layers. A crash in a leaf actor's app task unwinds that
|
|
|
|
|
actor's trio tree, ships across its IPC channel as a typed
|
|
|
|
|
``Error`` msg, and unwinds the parent's trio tree in turn — the
|
|
|
|
|
"SC-transitive supervision protocol" from the README's pitch.
|
|
|
|
|
The whole tree cancels and errors like one big trio program; it
|
|
|
|
|
just happens to be spread across processes.
|
|
|
|
|
|
|
|
|
|
One actor, one process, one ``trio.run()``
|
|
|
|
|
------------------------------------------
|
|
|
|
|
|
|
|
|
|
A ``tractor`` "actor" is not a green thread, nor an object with
|
|
|
|
|
a mailbox, nor a coroutine: it's one OS process running one
|
|
|
|
|
:func:`trio.run` whose root task boots the runtime machinery —
|
|
|
|
|
msg loop, RPC task scheduler, IPC server — all embodied by a
|
|
|
|
|
single :class:`tractor.Actor` instance.
|
|
|
|
|
|
|
|
|
|
.. margin:: Shared nothing
|
|
|
|
|
|
|
|
|
|
Processes buy you a real `shared nothing architecture`_: no
|
|
|
|
|
accidentally-shared mutable state, no GIL contention, and
|
|
|
|
|
every actor can be inspected (or killed) like any other OS
|
|
|
|
|
process.
|
|
|
|
|
|
|
|
|
|
You rarely construct an :class:`~tractor.Actor` yourself; the
|
|
|
|
|
runtime makes exactly one per process and you grab it with
|
|
|
|
|
:func:`tractor.current_actor`:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
import tractor
|
|
|
|
|
|
|
|
|
|
actor = tractor.current_actor() # NoRuntime if none running
|
|
|
|
|
print(actor.aid.name) # str name, need not be unique
|
|
|
|
|
print(actor.aid.uuid) # uuid4 str, IS unique
|
|
|
|
|
print(actor.aid.pid) # the OS pid
|
|
|
|
|
print(actor.uid) # legacy (name, uuid) pair
|
|
|
|
|
|
|
|
|
|
Identity is carried by the ``Aid`` msg-struct (see
|
|
|
|
|
``tractor.msg.types``): a ``name``/``uuid``/``pid`` triple
|
|
|
|
|
exchanged in the very first "mailbox handshake" whenever two
|
|
|
|
|
actors connect. It's what the registrar stores and what shows up
|
|
|
|
|
in logs and proc-titles. The older ``.uid`` 2-tuple of
|
|
|
|
|
``(name, uuid)`` predates ``Aid`` and is still pervasive across
|
|
|
|
|
the codebase; treat it as the legacy spelling of the same
|
|
|
|
|
identity.
|
|
|
|
|
|
|
|
|
|
If this smells like the `actor model`_, sure — but as the
|
|
|
|
|
README warns, it probably doesn't look like what you *think* an
|
|
|
|
|
actor model looks like, and that's intentional. Here an "actor"
|
|
|
|
|
is purely a runtime-unit-of-abstraction: process +
|
|
|
|
|
``trio.run()`` + IPC machinery.
|
|
|
|
|
|
|
|
|
|
IPC: channels, transports, addresses
|
|
|
|
|
------------------------------------
|
|
|
|
|
|
|
|
|
|
Two connected actors talk through a :class:`tractor.Channel`: a
|
|
|
|
|
duplex, per-peer msg pipe. Each ``Channel`` wraps a
|
|
|
|
|
``MsgTransport`` instance which does the wire work: framing,
|
|
|
|
|
encode/decode and the socket itself. The encoding is msgpack
|
|
|
|
|
(via msgspec_) and *every* msg is an instance of one of the
|
|
|
|
|
runtime's tagged-union :class:`msgspec.Struct` types: the
|
|
|
|
|
``Aid`` handshake, ``Start``/``StartAck`` (RPC init),
|
|
|
|
|
``Started``/``Yield``/``Stop``/``Return`` (the ctx dialog
|
|
|
|
|
phases), ``Error``, etc. There is no raw-bytes mode; the
|
|
|
|
|
msg-spec *is* the protocol, which is exactly what lets payloads
|
|
|
|
|
be type-limited per-context (see ``pld_spec`` in
|
|
|
|
|
:doc:`/guide/context`).
|
|
|
|
|
|
|
|
|
|
Addresses come in two spellings:
|
|
|
|
|
|
|
|
|
|
- *unwrapped*: the plain-tuple form you pass to user APIs —
|
|
|
|
|
``('127.0.0.1', 1616)`` for tcp, or a
|
|
|
|
|
``(<filedir>, <filename>)`` path-pair for uds;
|
|
|
|
|
- *wrapped*: the internal ``TCPAddress``/``UDSAddress`` struct
|
|
|
|
|
types (plus libp2p-style multiaddr helpers over in
|
|
|
|
|
``tractor.discovery``).
|
|
|
|
|
|
|
|
|
|
You only ever need the tuple form; the runtime wraps and
|
|
|
|
|
unwraps at the boundaries.
|
|
|
|
|
|
|
|
|
|
TCP: the boring default
|
|
|
|
|
***********************
|
|
|
|
|
|
|
|
|
|
The default transport (``'tcp'``) binds each actor's IPC server
|
|
|
|
|
to loopback ``('127.0.0.1', <random port>)`` unless told
|
|
|
|
|
otherwise, and is the only choice when your tree spans hosts.
|
|
|
|
|
Nothing exotic: ``trio`` TCP streams + length-prefixed msgpack
|
|
|
|
|
framing.
|
|
|
|
|
|
|
|
|
|
UDS: same-host, creds included
|
|
|
|
|
******************************
|
|
|
|
|
|
|
|
|
|
Pass ``enable_transports=['uds']`` and actors instead talk over
|
|
|
|
|
unix-domain sockets, with socket files placed in the per-user
|
|
|
|
|
runtime dir (``$XDG_RUNTIME_DIR/tractor/`` on linux, the
|
|
|
|
|
``platformdirs`` equivalent elsewhere). Two perks over tcp on a
|
|
|
|
|
single host:
|
|
|
|
|
|
|
|
|
|
- no ports to fight over; addrs are just file paths,
|
|
|
|
|
- the kernel snitches on your peer for free: the listening side
|
|
|
|
|
reads the connector's ``pid`` (plus ``uid``/``gid`` on linux)
|
|
|
|
|
straight off the socket via ``SO_PEERCRED`` /
|
|
|
|
|
``LOCAL_PEERPID`` — no extra handshake msgs required B)
|
|
|
|
|
|
|
|
|
|
.. warning::
|
|
|
|
|
|
|
|
|
|
Socket-file lifetime == listening actor lifetime. On
|
|
|
|
|
listener teardown the runtime ``os.unlink()``\s the socket
|
|
|
|
|
file immediately, so any *late* connection attempt (say, a
|
|
|
|
|
sub-actor racing to deregister with a registrar that's
|
|
|
|
|
already shutting down) fails with ``FileNotFoundError``.
|
|
|
|
|
And ofc, UDS is same-host only.
|
|
|
|
|
|
|
|
|
|
Here's a full actor tree run entirely over uds:
|
|
|
|
|
|
|
|
|
|
.. literalinclude:: ../../examples/uds_transport_actor_tree.py
|
|
|
|
|
:caption: examples/uds_transport_actor_tree.py
|
|
|
|
|
:language: python
|
|
|
|
|
|
|
|
|
|
Picking a transport
|
|
|
|
|
*******************
|
|
|
|
|
|
|
|
|
|
Transport choice is per-actor via the ``enable_transports``
|
|
|
|
|
kwarg accepted by :func:`tractor.open_root_actor` (and proxied
|
|
|
|
|
through ``open_nursery()`` when it implicitly boots the
|
|
|
|
|
runtime), plus per-child via
|
|
|
|
|
``ActorNursery.start_actor(enable_transports=...)``. Two rules
|
|
|
|
|
the runtime enforces today:
|
|
|
|
|
|
|
|
|
|
- exactly ONE transport per actor: multi-transport actors are
|
|
|
|
|
on the roadmap but currently raise ``RuntimeError``;
|
|
|
|
|
- your ``registry_addrs`` protos must all be in
|
|
|
|
|
``enable_transports``: mismatches fail fast with
|
|
|
|
|
``ValueError`` instead of (as in darker times) hanging the
|
|
|
|
|
registrar handshake forever.
|
|
|
|
|
|
|
|
|
|
Spawn backends
|
|
|
|
|
--------------
|
|
|
|
|
|
|
|
|
|
How does an actor actually *become* a process? Via a swappable
|
|
|
|
|
spawn backend, selected with the ``start_method`` kwarg to
|
|
|
|
|
:func:`tractor.open_root_actor`:
|
|
|
|
|
|
|
|
|
|
``'trio'`` (default)
|
|
|
|
|
The home-grown spawner: re-exec the child as
|
|
|
|
|
``python -m tractor._child`` using ``trio``'s subprocess
|
|
|
|
|
machinery, then bootstrap it over the first IPC exchange
|
|
|
|
|
(the parent ships a ``SpawnSpec`` msg carrying all init
|
|
|
|
|
state). Supported on all platforms and the most battle
|
|
|
|
|
tested choice by far.
|
|
|
|
|
|
|
|
|
|
``'mp_spawn'`` / ``'mp_forkserver'``
|
|
|
|
|
The stdlib :mod:`multiprocessing` start-methods of the same
|
|
|
|
|
names (forkserver is posix-only). Mostly interesting for
|
|
|
|
|
ecosystem compat and start-up-latency tuning.
|
|
|
|
|
|
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
|
|
|
``'subint'`` (in development, py3.14+)
|
|
|
|
|
On the roadmap (not yet selectable via ``start_method`` on
|
|
|
|
|
this release): run each actor as a `PEP 734`_
|
|
|
|
|
sub-interpreter (``concurrent.interpreters``) driven on its
|
|
|
|
|
own OS thread *inside the parent process* — interpreter-level
|
|
|
|
|
shared-nothing isolation with much faster start-up. Yes, this
|
|
|
|
|
bends the one-actor-one-process rule; the rest of the model
|
|
|
|
|
is unchanged.
|
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 ``TRACTOR_SPAWN_METHOD`` env-var beats any caller-passed
|
|
|
|
|
``start_method``, so you can swap backends under an unmodified
|
|
|
|
|
app:
|
|
|
|
|
|
|
|
|
|
.. code:: bash
|
|
|
|
|
|
|
|
|
|
TRACTOR_SPAWN_METHOD=mp_forkserver python my_app.py
|
|
|
|
|
|
|
|
|
|
One current limitation worth knowing: ``debug_mode=True`` (the
|
|
|
|
|
crash-to-REPL machinery) is only supported on backends whose
|
|
|
|
|
child-side runtime is trio-native, e.g. the default ``'trio'``;
|
|
|
|
|
see :doc:`/guide/debugging` for the deats.
|
|
|
|
|
|
|
|
|
|
The registrar
|
|
|
|
|
-------------
|
|
|
|
|
|
|
|
|
|
Discovery needs a phonebook. Every actor, as part of boot,
|
|
|
|
|
registers its ``Aid`` and bind-addrs with the *registrar*: an
|
|
|
|
|
otherwise ordinary actor (a :class:`tractor.Registrar`, subtype
|
|
|
|
|
of :class:`~tractor.Actor`) that keeps the name -> addrs table
|
|
|
|
|
for the tree; on graceful exit each actor de-registers itself.
|
|
|
|
|
|
|
|
|
|
.. margin:: Default registry addrs
|
|
|
|
|
|
|
|
|
|
With no ``registry_addrs`` passed:
|
|
|
|
|
|
|
|
|
|
- tcp: ``('127.0.0.1', 1616)``
|
|
|
|
|
- uds: ``registry@1616.sock``
|
|
|
|
|
in the runtime dir
|
|
|
|
|
|
|
|
|
|
Who *is* the registrar? Decided at root boot, rendezvous style.
|
|
|
|
|
:func:`tractor.open_root_actor` probes each addr in
|
|
|
|
|
``registry_addrs`` with a quick connect-ping, then:
|
|
|
|
|
|
|
|
|
|
- **somebody answered**: this root is a plain actor; it
|
|
|
|
|
registers with the existing registrar and binds random
|
|
|
|
|
same-proto addrs for its own IPC server;
|
|
|
|
|
- **nobody answered**: this root *becomes* the registrar and
|
|
|
|
|
binds the registry addrs itself.
|
|
|
|
|
|
|
|
|
|
So single-program trees need zero config — the root quietly
|
|
|
|
|
self-appoints — while multi-program setups share a registrar by
|
|
|
|
|
pointing every program at the same ``registry_addrs``. Pass
|
|
|
|
|
``ensure_registry=True`` to demand that *this* call create the
|
|
|
|
|
registry; it raises if the addrs are already served.
|
|
|
|
|
|
|
|
|
|
The lookup APIs — :func:`tractor.find_actor`,
|
|
|
|
|
:func:`tractor.wait_for_actor`, :func:`tractor.query_actor` and
|
|
|
|
|
:func:`tractor.get_registry` — all consult it (after first
|
|
|
|
|
checking already-connected peers):
|
|
|
|
|
|
|
|
|
|
.. literalinclude:: ../../examples/service_daemon_discovery.py
|
|
|
|
|
:caption: examples/service_daemon_discovery.py
|
|
|
|
|
:language: python
|
|
|
|
|
|
|
|
|
|
If you bump into "arbiter" in old issues or posts: that's the
|
|
|
|
|
legacy name for the same thing, surviving in-code only as the
|
|
|
|
|
``Arbiter = Registrar`` class alias; all current terminology is
|
|
|
|
|
"registrar"/"registry". Fair warning per the README: this is
|
|
|
|
|
still a **very naive** discovery sys (no re-election, no gossip
|
|
|
|
|
protocol... yet) and a registrar is expected to outlive its
|
|
|
|
|
registrants.
|
|
|
|
|
|
|
|
|
|
Runtime env vars
|
|
|
|
|
----------------
|
|
|
|
|
|
|
|
|
|
A few env-vars let you re-tune a whole tree *without touching
|
|
|
|
|
app code*; each wins over its corresponding kwarg:
|
|
|
|
|
|
|
|
|
|
.. list-table::
|
|
|
|
|
:header-rows: 1
|
|
|
|
|
:widths: 30 44 26
|
|
|
|
|
|
|
|
|
|
* - env-var
|
|
|
|
|
- effect
|
|
|
|
|
- vs. kwarg
|
|
|
|
|
* - ``TRACTOR_LOGLEVEL``
|
|
|
|
|
- crank (or silence) console-log verbosity for every actor
|
|
|
|
|
in the tree
|
|
|
|
|
- beats ``loglevel``
|
|
|
|
|
* - ``TRACTOR_SPAWN_METHOD``
|
|
|
|
|
- swap the process spawn backend
|
|
|
|
|
- beats ``start_method``
|
|
|
|
|
* - ``TRACTOR_ENABLE_STACKSCOPE``
|
|
|
|
|
- install the ``SIGUSR1`` task-tree-dump handler in every
|
|
|
|
|
actor, even outside ``debug_mode`` (see
|
|
|
|
|
:doc:`/guide/debugging`)
|
|
|
|
|
- OR'd with ``enable_stack_on_sig``
|
|
|
|
|
|
|
|
|
|
Spotting actors from your shell
|
|
|
|
|
*******************************
|
|
|
|
|
|
|
|
|
|
Every sub-actor sets an OS-level proc-title of the form
|
|
|
|
|
``_subactor[<name>@<pid>]`` (via ``setproctitle``, silently
|
|
|
|
|
skipped when not installed) so ``ps``/``htop``/``pstree`` show
|
|
|
|
|
*which actor is which* at a glance. The README's signature
|
|
|
|
|
incantation — watch a tree build and self-destruct live:
|
|
|
|
|
|
|
|
|
|
.. code:: bash
|
|
|
|
|
|
|
|
|
|
$TERM -e watch -n 0.1 "pstree -a $$" \
|
|
|
|
|
& python examples/nested_actor_tree.py \
|
|
|
|
|
&& kill $!
|
|
|
|
|
|
|
|
|
|
For scripting there are two stable cmdline markers:
|
|
|
|
|
|
|
|
|
|
.. code:: bash
|
|
|
|
|
|
|
|
|
|
pgrep -fa '_subactor\[' # live, titled sub-actors
|
|
|
|
|
pgrep -fa 'tractor._child' # 'trio'-backend children not
|
|
|
|
|
# yet (re)titled
|
|
|
|
|
|
|
|
|
|
The title also lands in the kernel ``comm`` (truncated to ~15
|
|
|
|
|
bytes) which survives into zombie state — that's what
|
|
|
|
|
``tractor``'s own test-harness reapers key off. To be crystal
|
|
|
|
|
clear about the contract though: you should never *need* a
|
|
|
|
|
reaper; if you can create zombie child processes (without using
|
|
|
|
|
a system signal) it **is a bug** — please report it!
|
|
|
|
|
|
|
|
|
|
Logging
|
|
|
|
|
-------
|
|
|
|
|
|
|
|
|
|
The runtime logs through a thin adapter over stdlib
|
|
|
|
|
:mod:`logging` that stamps every record with actor + task info.
|
|
|
|
|
Two calls get you going:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
from tractor.log import get_console_log, get_logger
|
|
|
|
|
|
|
|
|
|
log = get_logger(__name__) # actor/task-aware sub-logger
|
|
|
|
|
get_console_log('info') # attach console handler @ level
|
|
|
|
|
|
|
|
|
|
(or just pass ``loglevel='info'`` to
|
|
|
|
|
:func:`tractor.open_root_actor` and the console handler comes up
|
|
|
|
|
with the runtime).
|
|
|
|
|
|
|
|
|
|
``tractor`` adds custom levels — and matching logger methods —
|
|
|
|
|
that slot between the stdlib ones so you can dial in *which
|
|
|
|
|
runtime subsystem* you want to hear from: ``.transport()`` (5),
|
|
|
|
|
``.runtime()`` (15), ``.devx()`` (17), ``.cancel()`` (22), plus
|
|
|
|
|
a ``PDB`` (500) level for debugger chatter. E.g.
|
|
|
|
|
``loglevel='cancel'`` plays the whole cancellation chorus while
|
|
|
|
|
staying quiet about transport-layer noise. Beyond that
|
|
|
|
|
``tractor`` isn't opinionated about how you consume logs: it's
|
|
|
|
|
all stdlib ``logging`` underneath.
|
|
|
|
|
|
|
|
|
|
Where to next?
|
|
|
|
|
--------------
|
|
|
|
|
|
|
|
|
|
.. seealso::
|
|
|
|
|
|
|
|
|
|
- :doc:`/guide/context` — the SC-linked cross-actor task API
|
|
|
|
|
that rides on every ``Channel``.
|
|
|
|
|
- :doc:`/guide/debugging` — ``debug_mode``, the multi-process
|
|
|
|
|
REPL and ``stackscope`` task-tree dumps.
|
|
|
|
|
- :doc:`/explain/sc-distributed` — *why*
|
|
|
|
|
one-actor-one-process, and what kind of "actor model" this
|
|
|
|
|
is (and isn't).
|
|
|
|
|
|
|
|
|
|
.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
|
|
|
|
|
.. _trio: https://github.com/python-trio/trio
|
|
|
|
|
.. _actor model: https://en.wikipedia.org/wiki/Actor_model
|
|
|
|
|
.. _shared nothing architecture: https://en.wikipedia.org/wiki/Shared-nothing_architecture
|
|
|
|
|
.. _msgspec: https://jcristharif.com/msgspec/
|
|
|
|
|
.. _PEP 734: https://peps.python.org/pep-0734/
|