March Docs

NodeRpc

NodeRpc: the receive-side RPC dispatcher + caller-side reply logic (P1 L4).

The pure orchestration that sits between the net-kernel transport and a node's enrolled targets. The responder decodes nothing here (it works on an already- decoded CallRequest): it looks the target up by identity, runs the CAS admission check (RemoteCall.verify), invokes the local marshalling stub, and builds the CallReply to frame back. The caller side interprets a reply and makes the failure-isolation decisions (deadline expiry, peer-down).

The target table is injected here. In the full system the compiler-emitted enroll/stub mechanism (a function-by-identity registry + marshalling stubs) populates it; modelling it as an injected Targets keeps this layer pure and unit-testable and pins down exactly what that mechanism must provide: per target, its (sig_hash, impl_hash) and an invoke : args-bytes -> result-bytes stub (Err models a crash/exit inside the target body).

Types

typeTargetTarget = {#

A locally-enrolled remote target: CAS identity + marshalling invoker.

typeTargetsTargets = Targets(Map(String, Target))#

name (module.fn) -> Target.

Functions

fnemptyempty() : Targets do Targets(Map.empty()) end#

A target table with nothing enrolled (handle_request then serves only the compiler-registered remote stubs).

fnenrollenroll(t : Targets, module_name : String, fn_name : String, target : Target) : Targets#

[t] with [target] enrolled under "<module_name>.<fn_name>", replacing any target already enrolled under that name.

let t = NodeRpc.enroll(NodeRpc.empty(), "Math", "double",
  { sig_hash: sh, impl_hash: ih, invoke: fn args -> Ok(args) })
fnhandle_framehandle_frame(t : Targets, bytes : List(Int)) : Option(CallReply)#

Decode an inbound request frame [bytes] and dispatch it (handle_request). None for a frame that does not decode as a CallRequest: the sender framed garbage, so there is nothing to answer.

fnhandle_requesthandle_request(t : Targets, req : CallRequest) : CallReply#

Run an inbound request [req] against the local target table [t] and produce the reply to frame back, carrying the request's correlation. An enrolled target is checked with RemoteCall.verify (sig_hash mismatch: TypeMismatch; impl_hash mismatch: VersionSkew) and then invoked; its Ok(payload) is the reply's result, its Err(reason) (a crash inside the target body) is RemoteExit(reason).

When [t] has no entry for the reference, falls back to the runtime's remote registry, populated by compiler-emitted march_remote_register calls at startup: no stub under the impl_hash is NoTarget, a sig_hash mismatch TypeMismatch. This makes NodeCall.serve_one(NodeRpc.empty(), fd) work for any module that defines remote stubs.

fninterpretinterpret(reply : CallReply) : Result(List(Int), CallError)#

A reply as the caller's Result: Ok(payload) for Returned(payload), Err(e) for Failed(e).

fnmatchesmatches(req : CallRequest, reply : CallReply) : Bool#

Does [reply] answer [req]: do their correlation numbers agree? (Replies on one connection interleave.)

fnpeer_down_errorpeer_down_error() : CallError do NoConnection end#

The error a caller synthesizes when the failure detector has declared the target's node dead (a pending call cannot complete): NoConnection.

fntimed_outtimed_out(now : Int, deadline : Int) : Bool do now >= deadline end#

Failure isolation: has [deadline] passed at [now] (both in the same unit, ms)? True when now >= deadline, so a deadline equal to now has passed.