Find a file
russell@unturf.com ac4636e00c
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.
2026-06-16 11:33:28 -04:00
docs phase 5: Node.call() gen_server protocol 2026-06-16 11:33:28 -04:00
erldistpy phase 5: Node.call() gen_server protocol 2026-06-16 11:33:28 -04:00
tests phase 5: Node.call() gen_server protocol 2026-06-16 11:33:28 -04:00
.gitignore phase 0 + 1: repo bones and ETF codec 2026-06-16 10:39:01 -04:00
LICENSE phase 0 + 1: repo bones and ETF codec 2026-06-16 10:39:01 -04:00
Makefile phase 0 + 1: repo bones and ETF codec 2026-06-16 10:39:01 -04:00
pyproject.toml phase 0 + 1: repo bones and ETF codec 2026-06-16 10:39:01 -04:00
README.md phase 0 + 1: repo bones and ETF codec 2026-06-16 10:39:01 -04:00

erldistpy

Native Python client for our Erlang distribution protocol. Talk Erlang/Elixir nodes from CPython without HTTP shim layers.

Built to swap into unfeed's WalletTransport so a Python web app can call Unsandbox.WalletRPC over native dist instead of HTTPS. Same gen_call semantics, lower latency, fewer moving parts.

Status

Phase 0 — repo bones. See docs/ROADMAP.md for the phase plan.

Quick start

make bootstrap   # create venv, install editable + dev deps
make test        # run pytest
make lint        # ruff check

Scope

  • ETF (External Term Format) codec — encode/decode Erlang terms
  • EPMD client — node name → port lookup
  • Distribution handshake — cookie auth, version negotiation
  • gen_call to registered processes on a remote node
  • TLS dist support (Erlang inet_tls_dist)

Out of scope: full Erlang node impersonation, link/monitor lifecycles, distributed Mnesia. We are a client, not a peer node.

Why not Pyrlang?

Pyrlang implements a full asyncio Erlang node. Heavy, asyncio-first, complex. We need a small synchronous client that fits behind the same WalletTransport Protocol as httpx. Different shape.

License

Unlicense (public domain).