Channel and communication
The parent-child channel (client and frame) and the communication mixin.
Communication capability: the first mixin over the base server (D17).
The base server is born WITHOUT channels. This mixin adds the communication
capability as member objects built by its cooperative __init__:
parent_channel — the ChannelClient of ◆D10, ARMED iff
parent=<address> was given — and children_channel — the hub side, a
placeholder member the orchestration package arms (the minimal package knows
how to BE a child, not how to HAVE children). Accessing an unarmed side
raises RuntimeError (“not armed”); a composition WITHOUT the mixin simply
lacks the attributes — a different type, not a ghost.
This is the first REAL proof of the D16 cooperative contract: composed
BEFORE the server class (class MyServer(CommunicationMixin, BaseServer))
the mixin peels its own kwargs, forwards the rest, and hooks the lifespan
without the base knowing it — __call__ cooperatively intercepts the
lifespan scope: an armed parent side pre-receives lifespan.startup,
connects (and REGISTERs) BEFORE any app hook runs, and disconnects when the
protocol completes at shutdown. An unreachable hub answers
lifespan.startup.failed and re-raises: a child that cannot register must
die, never serve detached.
Registration makes the registrant a child in the tree even when not spawned
by us (communication ≠ process lifecycle, D17): the REGISTER frame presents
{"name", "pid"}; the name is derived from the class name and the pid
(child naming proper belongs to the orchestration package).
- class genro_asgi.communication.CommunicationMixin(**kwargs)[source]
Bases:
objectCommunication capability mixin, composed BEFORE a server class.
Constructor kwargs peeled here:
parent— the address of the parent hub (uds:<path>|tcp:<host>:<port>); when given, the parent side is armed with aChannelClient.- Parameters:
kwargs (Any)
- property parent_channel: ChannelClient
The channel to the parent hub; unarmed access is an error.
Channel client — the child side of the parent↔child channel.
Knowing how to BE a child is part of what a server IS (SPECIFICATION.md ◆D10): the minimal package ships the frame protocol and this client; the hub (parent side) lives in the orchestration package and imports the protocol from below, never the reverse.
connect() retries with short backoff until connect_timeout (boot
race: the hub socket may not be bound yet) and presents the child with a
REGISTER frame (data={"name", "pid"}). Steady state is fire-and-forget
frames in both directions. There is no steady-state reconnection: when the
hub side goes away (EOF — the connection-loss signal),
on_orphan(client) fires and the child is expected to terminate cleanly.
A deliberate close() fires no orphan signal.
Callbacks (on_message(frame), on_orphan(client)) may be sync or
async; an exception raised by a callback is logged and never severs the
channel — a consumer bug must not fake a member death.
Addresses:
uds:/path/to/hub.sock Unix domain socket (default)
tcp:127.0.0.1:8731 TCP (multi-host door)
- class genro_asgi.channel.client.ChannelClient(address, name, *, on_message=None, on_orphan=None, connect_timeout=10.0, max_size=None)[source]
Bases:
objectChild-side endpoint: connect to the hub, present itself, relay frames.
- Parameters:
- async connect()[source]
Connect with boot-time retry/backoff, present the REGISTER frame.
- Return type:
- async wait_closed()[source]
Block until the channel ends (either side); the child’s main wait.
- Return type:
Versioned channel frames with bounded JSON routing information and opaque bytes.
The frame layer never interprets payload bytes. Socket and in-process streams
share FrameCodec, including strict version, length and JSON checks.
- exception genro_asgi.channel.frame.FrameTooLarge(size, maximum)[source]
Bases:
ValueErrorThe complete frame exceeds policy; encoding rejects before any write.