phase 5: Node.call() gen_server protocol

Node wraps EPMD lookup + handshake + Channel into a single client
object. Constructor eagerly opens the dist connection; call() runs the
synchronous $gen_call protocol against a registered name on the peer:

  caller -> {'$gen_call', {FromPid, Ref}, Request}    (REG_SEND)
  server -> {Ref, Reply}                              (SEND)

Synthesized FromPid and a Node-lifetime Ref counter route replies back
to us; mismatched Ref or unexpected control op raises CallProtocolError.
Reply timeout raises CallTimeout (also covers Erlang's silent-drop case
when the registered name doesn't exist).

Tests against an erl peer running a $gen_call-aware loop:
  - {ping, X} -> {pong, X}
  - {add, A, B} -> {ok, A + B}
  - five sequential calls with monotonically increasing Refs
  - server error response surfaces as Python tuple
  - slow responder triggers CallTimeout
  - unknown registered name surfaces as CallTimeout
  - ref uniqueness across 100 synthesized refs

111 tests green across 10 consecutive runs, lint clean.
This commit is contained in:
russell@unturf.com 2026-06-16 11:33:28 -04:00
parent 776efaead3
commit ac4636e00c
No known key found for this signature in database
4 changed files with 383 additions and 4 deletions

View file

@ -62,11 +62,22 @@ if we hit a peer that requires it.
process: EPMD → handshake → REG_SEND → recv reply, payload matches
- Survives an outbound tick before the reply
## Phase 5 — gen_call convenience layer
## Phase 5 — gen_call convenience layer
- `Node.call(name_or_pid, request, timeout=5.0)` → reply term
- Wraps the `$gen_call` protocol used by `:gen_server`
- Idempotency-key handling lives in the caller; we just pass the term
- `Node(our_name, peer_name, cookie)` lazy-connects: EPMD lookup +
TCP connect + v6 handshake on construction
- `Node.call(target_name, request, timeout=5.0)` implements
the `$gen_call` protocol:
```
caller -> {'$gen_call', {FromPid, Ref}, Request} (REG_SEND)
server -> {Ref, Reply} (SEND)
```
- Unique Ref per call, matched on receive — protocol violations
surface as `CallProtocolError`, timeouts as `CallTimeout`
- Tests: `{ping, X} -> {pong, X}`, `{add, A, B} -> {ok, A+B}`,
sequential calls without crosstalk, timeout against a slow
responder, drop-on-the-floor against an unknown registered name,
Ref uniqueness across the Node lifetime
## Phase 6 — TLS dist