Managoat.Runner.Connection (managoat_runner v0.1.0)

Copy Markdown View Source

The server end of one runner daemon's socket.

A WebSock handler: it registers itself with the Managoat.Runner.Host under the runner's id, turns call/3 requests from anywhere the host can route from into JSON frames, matches the daemon's replies back to the callers, and forwards the daemon's unsolicited stream frames to the owner of the command they belong to as the standard Managoat.Sandbox owner messages.

The init map the host application hands WebSockAdapter.upgrade/4:

%{runner_id: "…", name: "mini", meta: %{...}, host: MyApp.RunnerHost}

runner_id is required. name is the runner's display name, used in log lines and in the 4409 close reason. meta (default %{}) is opaque and is handed to the host on register/2 and presence/3. host overrides the configured Managoat.Runner.Host for this connection; see Managoat.Runner.Config.

Wire protocol

Text frames, JSON objects. Platform → daemon, one request per id:

{"id": 7, "op": "spawn", "name": "runner-…", "cmd": "…", "args": [...], ...}

Daemon → platform, one reply per request plus unsolicited stream frames:

{"id": 7, "ok": true,  "result": {...}}
{"id": 7, "ok": false, "error": "not_found", "detail": "…"}
{"stream": "stdout" | "stderr", "session_id": "s-…", "data": "<base64>"}
{"stream": "exit",  "session_id": "s-…", "code": 0}
{"stream": "stdout", "session_id": "s-…", "data": "…", "replay_for": 7}

A spawn/attach request names the owner and ref its session's frames should reach; the subscription is installed before the reply is delivered so no frame can slip past it. The daemon sends the reply first, then replays the session's journal from byte zero tagged replay_for with that request's id — those frames reach only the subscriber that request installed, so a second attacher's replay never duplicates output at the first owner — and then streams live frames, which reach every subscriber of the session.

Failure

When the socket closes, every caller still waiting gets {:error, {:unavailable, :runner_disconnected}} and every subscribed owner gets {:error, %{ref: ref}, :runner_disconnected} — a transport failure in the contract's terms, after which no :exit follows. Detached sessions on the daemon keep running; that is what reattach is for.

Summary

Functions

Send a request to the runner and wait for its reply. The connection process is found through the configured host's whereis/1.

Stop forwarding a session's frames to ref's owner (this end only).

Functions

call(runner_id, payload, opts \\ [])

@spec call(binary(), map(), keyword()) :: {:ok, map()} | {:error, term()}

Send a request to the runner and wait for its reply. The connection process is found through the configured host's whereis/1.

Options: :timeout (ms or :infinity, default 30000); :subscribe{owner_pid, ref} for spawn/attach, installed on the session id the reply names before the reply is returned.

Returns {:ok, result} / {:error, reason} with the daemon's error string already normalized into the sandbox error taxonomy, or {:error, {:unavailable, :runner_offline | :runner_disconnected | :runner_timeout}}.

unsubscribe(runner_id, session_id, ref)

@spec unsubscribe(binary(), String.t(), reference()) :: :ok

Stop forwarding a session's frames to ref's owner (this end only).