7.0 KiB
tractor over AF_TIPC, where the address is the service name
TIPC is a linux-kernel cluster IPC protocol whose service names live in a cluster-wide name table maintained by the kernel. For tractor that means:
- an actor’s IPC address is a service name
(stype, instance), not a host/port, .bind()ing it is service registration,- a peer’s
.connect()-by-name is the lookup.
So the discovery machinery tractor.discovery normally implements with a registrar actor comes for free, in-kernel — which is the ask in gh #378.
Why
examples/multihost/?tests/test_docs_examples.pywalksexamples/recursively and runs everything it collects as a subproc, assertingrc == 0. These need thetipckernel module (and, for the two-host pair, a live bearer), so they can’t satisfy that;'multihost' not in p[0]is already in the test’s exclusion list, which is what keeps them out of CI. See “CI” below for the separate matrix-entry plan.
the single best demo
sudo modprobe tipc
python single_host.pyFour actors boot, four service names appear in the kernel’s table, and all four are withdrawn on teardown — observed with tipc(8), entirely outside tractor:
--- `tipc nametable show` :: root + 3 subactors ---
Type Lower Upper Scope Port
1953628160 1616 1616 cluster 3161982128
1953628160 1219427151 1219427151 cluster 1587358717
1953628160 2641339936 2641339936 cluster 1864021571
1953628160 3344505866 3344505866 cluster 3816483388
--- `tipc nametable show` :: after teardown (all withdrawn) ---
Type Lower Upper Scope Port
1953628160 is 0x74720000 — tractor’s reserved service type, ascii tr in the high half. 1616 is the host-singleton registrar, the same idiom as the TCP port and the registry@1616.sock UDS filename. The other three instances are per-actor digests (see “silent crosstalk” below).
push-based discovery
python watch_nametable.pySubscribes to the kernel’s topology service and prints name table transitions as they happen — no polling, no registrar round-trip:
watching the TIPC name table..
[+] published instance=1616 port=0x00000000:2375440573
spawning subactors..
[+] published instance=186947472 port=0x00000000:3960753074
[+] published instance=2191362136 port=0x00000000:2263898853
[+] published instance=3484369663 port=0x00000000:2126817956
tearing down..
[-] withdrawn instance=186947472 port=0x00000000:3960753074
...
This is the groundwork for a push registry in tractor.discovery._registry (gh #184, #216) — a registrar that never polls find_actor().
two hosts
Everything above is single-node (modprobe is enough). To span hosts you need a bearer on both, which is the one thing that can’t be CI’d.
# on BOTH hosts
sudo modprobe tipc
# over ethernet (L2) — simplest when the hosts share a segment
sudo tipc bearer enable media eth device eth0
# ..or over UDP when L2 isn't available (pairs nicely with the
# `wg` tunnel examples in ../wg_lan/)
sudo tipc bearer enable media udp name uc localip 10.0.11.1
# verify BEFORE running anything: this must list the peer
tipc link list
tipc node listThen:
# host A
python host_a_srv.py
# host B
python host_b_client.pyNote what’s absent from both scripts: any IP, hostname or port. Both sides name the same service, and the kernel routes it. Move host_a_srv.py to a third node and host B’s dial keeps working, unchanged.
scope
TIPCAddress._scope is the backend’s .bindspace — literally “the set of hosts this published name is reachable from”:
| scope | meaning |
|---|---|
TIPC_NODE_SCOPE |
same host only — the UDS analogue |
TIPC_CLUSTER_SCOPE |
cluster-visible (the default) |
TIPC_ZONE_SCOPE is deprecated and aliased to cluster by modern kernels; tractor accepts it on input and folds it, logging at transport level.
gotchas worth knowing before you deploy
Silent crosstalk. Unlike every other backend, a duplicate bind does not raise EADDRINUSE — TIPC happily accepts multiple publishers of one name and round-robins connects between them (verified: 6 dials alternated b,a,b,a,b,a). So an instance collision is silent traffic-splitting, not an error. That’s why TIPCAddress.get_random() derives the instance from a blake2b digest of the actor identity rather than a counter. Two tractor trees sharing both a cluster and an _stype share a name space; partition them by passing a distinct _stype.
Graceful close looks like a reset. A peer closing cleanly surfaces as BrokenResourceError/ECONNRESET rather than the clean 0-byte EOF you get from TCP/UDS. It’s benign — the transport layer already classifies it as a normal disconnect — but it does look alarming in transport-level logs.
Dialing an unpublished name answers EHOSTUNREACH instantly (no SYN-timeout wait), which is much better discovery-ping behaviour than TCP. tractor normalizes it to ConnectionError.
It’s opt-in, never a default. The module isn’t loaded on most boxes and doesn’t exist off-linux, so enable_transports=['tipc'] is always explicit. Check tractor.ipc._tipc.is_tipc_available() before assuming.
maddr form
There is no registered /tipc protocol in the multiaddr table yet (upstream track: gh #483 + multiformats/py-multiaddr#107), so the grammar is interim and str-only:
/tipc/<stype>/<instance>/<scope>
parse_maddr() special-cases this prefix before handing anything to Multiaddr(), which would otherwise reject the unregistered name outright. Registering it upstream is what would unblock gh #443’s “return Multiaddr everywhere” item.
running the suite over TIPC
The whole test suite runs under the backend:
sudo modprobe tipc
pytest --tpt-proto tipcWithout the module that fails loudly and immediately with an actionable message rather than a few hundred connect timeouts. Backend-specific unit tests live in tests/ipc/test_tipc.py and self-skip when the module is absent.
CI
Single-host TIPC is CI-able — the module ships with the standard Ubuntu kernel package, so a sudo modprobe tipc step plus a --tpt-proto tipc matrix entry should work. That’s not wired up yet; verify in a throwaway workflow first, and fall back to a container job with --cap-add NET_ADMIN if the runners refuse. Cross-node (bearer) testing stays manual — this README is that smoke test.
normative refs
The tipc.io docs are stale in places (gh #378 says as much). Treat the kernel sources as the only normative reference:
include/uapi/linux/tipc.h— address flavours, sockopts, the topologystructsnet/tipc/socket.c,net/tipc/topsrv.cman 8 tipc