macula_response behaviour (macula v11.1.0)

View Source

Behaviour for supervised RPC responses.

advertise/5 on the raw SDK takes a bare handler fun invoked in a transient process spawned per inbound CALL (see the internal macula_station_link advertise/5 — "Handlers run in a transient process spawned per CALL"). This module gives that transient process a proper shape: each inbound call starts one supervised macula_response child (under a simple_one_for_one factory this module owns), threading state through Module:init/1 and Module:handle_request/2, and publishing rpc.received_v1 / rpc.replied_v1 mesh facts around the request. This is the provider-side counterpart to macula_request.

A crashing Module:handle_request/2 kills the response child; that composes with the SDK's own crash mapping unchanged, since gen_server:call/3 against a dead callee raises the same way a crashing bare handler fun already does.

Example

   -module(math_service).
   -behaviour(macula_response).
   -export([init/1, handle_request/2]).
  
   init(_Args) -> {ok, []}.
  
   handle_request(#{a := A, b := B}, State) ->
       {reply, #{result => A + B}, State}.
   {ok, _Sup} = macula_response:advertise(Pool, Realm,
       <<"math.add_v1">>, math_service, []).

Advertise and publish functions

The options of advertise/6 and advertise_direct/7 take advertise, the function the handler is advertised with, macula:advertise/5 by default; publish_advertisement, the function advertise_direct/7 publishes its DHT record with, macula_direct_dial:publish_advertisement/5 by default; and fact_publish, the function each response announces its facts with, macula:publish/4 by default. The other options go on to those functions without these three. A test gives its own functions this way instead of replacing a module.

Direct-dial

advertise/5,6 registers the handler with the pool's advertise- gossip mechanism only — nothing published lets a caller on another station find this procedure without a route having propagated between the two stations first. advertise_direct/6 does that AND publishes a signed procedure_advertisement DHT record naming this pool's currently-connected station as the server, so a caller using macula_request:start_link_direct/6,7 can resolve and dial here directly, in one hop, regardless of whether the two stations have a routing edge between them.

Summary

Functions

Advertise Procedure on Pool/Realm. Starts a private factory supervisor for per-request response children and registers a dispatch handler with macula:advertise/5. Returns the supervisor pid so the caller can supervise it (or ignore it).

As advertise/5. Opts may include announce (default true), auth (forwarded to macula:advertise/5), and reuse_sup — an existing supervisor pid (as returned by a prior advertise/5,6 call) to register the handler again with, without starting a new factory supervisor. Use this for a periodic re-advertise (see advertise_direct/6,7's own doc) — calling plain advertise/5,6 on a timer would leak one orphaned supervisor per tick, since each call otherwise starts a fresh one.

As advertise/5, and additionally publishes a signed procedure_advertisement DHT record naming this pool's connected station as the server, so macula_request:start_link_direct/6,7 can resolve and dial here directly. NodeIdentity signs the advertisement and must be the node identity key Pool was started with: a caller targets that node_id, and the station knows the pool's connection by it.

As advertise_direct/6, with Opts forwarded BOTH to advertise/6 (so announce/auth/reuse_sup apply here too) and to macula_direct_dial:publish_advertisement/5, e.g. authorization, the provider authorization an org namespaced procedure needs (see macula_direct_dial's module doc, "Trust model"). Each side reads only the keys it recognizes, so one Opts map serves both. reuse_sup matters here specifically: the procedure's DHT record expires with its TTL, and callers reach the provider only through that record, so the provider republishes it — a periodic re-advertise with reuse_sup => Sup (the pid this function returned the first time) registers the handler again and republishes the DHT record without leaking a new supervisor per tick. cert_chain, a 10.x option authorization replaces, is refused with {error, {removed_option, cert_chain}} before the handler is registered.

Stop advertising. Does not stop the factory supervisor returned by advertise/5,6 — callers that want to tear it down should exit(Sup, shutdown) themselves.

Types

advertise/0

-type advertise() ::
          fun((macula:pool(), macula:realm(), macula:procedure(), macula_client:handler(), map()) ->
                  ok | {error, term()}).

advertise_opts/0

-type advertise_opts() ::
          #{advertise => advertise(),
            publish_advertisement => publish_advertisement(),
            fact_publish => macula_lifetime_announcer:publish(),
            atom() => term()}.

publish_advertisement/0

-type publish_advertisement() ::
          fun((macula:pool(), macula:realm(), macula:procedure(), macula_node_keys:node_key(), map()) ->
                  ok | {error, term()}).

Callbacks

handle_request/2

-callback handle_request(Payload :: term(), State :: term()) ->
                            {reply, Reply :: term(), NewState :: term()} |
                            {error, Reason :: term(), NewState :: term()}.

init/1

-callback init(Args :: term()) -> {ok, State :: term()} | {stop, Reason :: term()}.

terminate/2

(optional)
-callback terminate(Reason :: term(), State :: term()) -> any().

Functions

advertise(Pool, Realm, Procedure, Module, Args)

-spec advertise(macula:pool(), macula:realm(), macula:procedure(), module(), term()) ->
                   {ok, pid()} | {error, term()}.

Advertise Procedure on Pool/Realm. Starts a private factory supervisor for per-request response children and registers a dispatch handler with macula:advertise/5. Returns the supervisor pid so the caller can supervise it (or ignore it).

advertise(Pool, Realm, Procedure, Module, Args, Opts)

-spec advertise(macula:pool(), macula:realm(), macula:procedure(), module(), term(), advertise_opts()) ->
                   {ok, pid()} | {error, term()}.

As advertise/5. Opts may include announce (default true), auth (forwarded to macula:advertise/5), and reuse_sup — an existing supervisor pid (as returned by a prior advertise/5,6 call) to register the handler again with, without starting a new factory supervisor. Use this for a periodic re-advertise (see advertise_direct/6,7's own doc) — calling plain advertise/5,6 on a timer would leak one orphaned supervisor per tick, since each call otherwise starts a fresh one.

unadvertise(Pool, Realm, Procedure)

-spec unadvertise(macula:pool(), macula:realm(), macula:procedure()) -> ok.

Stop advertising. Does not stop the factory supervisor returned by advertise/5,6 — callers that want to tear it down should exit(Sup, shutdown) themselves.