macula_feeder behaviour (macula v11.1.0)
View SourceBehaviour for supervised content feeders (the put/share side).
start_link/4,5 returns immediately with a pid, delivers the outcome to Module:handle_fed/2, and publishes sharing.put_started_v1 / sharing.put_completed_v1 mesh facts around the transfer — including outcome => cancelled if the feeder is stopped before the put resolves.
This is content sharing, not general-purpose RPC streaming — see macula_streamer / macula_stream_sink for that (streaming.* facts belong to that pair).
Real cancel, real underneath
Internally this drives macula_content_transfer (PLAN_PUSH_UPLOAD.md Phase 4) rather than a blocking macula:put_content/2 call run in a linked worker — that blocking shape had no addressable handle to the actual transfer, so cancel/1 (gen_server:stop/1) could only ever kill the local worker process waiting on it, never touch the underlying stream. A macula_content_transfer cancelled that way doesn't even notice: nothing links a gen_server:call caller's death to the callee, so it would run to completion, or sit resolved-but-never-reaped, forever — orphaned, leaking its content_stream_bufs entry on the link and its macula_content_transfer_registry entry, for no purpose. This module now holds the macula_content_transfer pid directly (a content_transfer state field, alongside the lightweight resolve + await proxy worker, which asks this process to start the transfer) so cancel/1 can call macula_content_transfer:cancel/1 on it for real — the same peer-visible QUIC RESET_STREAM abort described there, not a local kill with nothing downstream the wiser. The share_id this module already minted for its own sharing.* mesh facts is threaded through as macula_content_transfer's own share_id too, so both layers resolve to the same id.
Direct-dial
start_link/4,5 puts through the pool's own connected link (whichever pick_connected_link/1 picks). start_link_direct/4,5 is the direct-dial counterpart: unlike macula_download's (which resolves an MCID to find out WHO has it), a PUT already knows its own target — the caller names Station directly, and it is resolved to a dialable endpoint via that station's own signed station_endpoint record (macula_direct_dial:resolve_station_endpoint/2, a fast, non-addressable DHT lookup that stays a plain blocking call inside the resolve+await proxy — nothing has ever needed to cancel mid-resolve) and dialed in one hop via macula_content_transfer: start_put_station/5, deliberately seeding that specific station instead of whichever the pool picks. See macula_direct_dial's module doc, "Content" section, for the trust model.
Transfer I/O
A feeder starts, awaits and cancels its transfer with start_put/3, start_put_station/5, await/1 and cancel/1, the macula_content_transfer ones by default; resolves its station for start_link_direct with resolve_station_endpoint/2, macula_direct_dial's by default; and announces its facts with fact_publish, macula:publish/4 by default. start_link/6 and start_link_direct/7 take them in their start options, the four transfer functions as transfer_io, checked by macula_content_transfer:transfer_io/2, and pass a link_io option on to the transfer they start. A function of another shape is refused with function_clause, in the caller.
Example
-module(doc_feeder).
-behaviour(macula_feeder).
-export([init/1, handle_fed/2]).
init(Parent) -> {ok, Parent}.
handle_fed(Result, Parent) ->
Parent ! {fed, Result},
{stop, normal, Parent}. {ok, Pid} = macula_feeder:start_link(doc_feeder, Pool, Realm,
Bytes, self()).
Summary
Functions
Cancel an in-flight feed. Publishes sharing.put_completed_v1 with outcome => cancelled if the put had not resolved yet.
Start a feeder. Puts Bytes into content storage via Pool.
As start_link/4, with Args passed to Module:init/1.
As start_link/5, with start options (see "Transfer I/O" above).
As start_link/4, but resolves Station's own station_endpoint and dials it directly instead of putting through the pool's existing links. See the "Direct-dial" section above.
As start_link_direct/5, with Args passed to Module:init/1.
As start_link_direct/6, with start options (see "Transfer I/O" above).
Types
-type start_opts() :: #{transfer_io => macula_content_transfer:transfer_io(), resolve_station_endpoint => fun((macula:pool(), macula_identity:pubkey()) -> {ok, binary()} | {error, term()}), fact_publish => macula_lifetime_announcer:publish(), link_io => macula_content_transfer:link_io()}.
Callbacks
Functions
-spec cancel(pid()) -> ok.
Cancel an in-flight feed. Publishes sharing.put_completed_v1 with outcome => cancelled if the put had not resolved yet.
-spec start_link(module(), macula:pool(), macula:realm(), binary()) -> {ok, pid()} | {error, term()}.
Start a feeder. Puts Bytes into content storage via Pool.
-spec start_link(module(), macula:pool(), macula:realm(), binary(), term()) -> {ok, pid()} | {error, term()}.
As start_link/4, with Args passed to Module:init/1.
-spec start_link(module(), macula:pool(), macula:realm(), binary(), term(), start_opts()) -> {ok, pid()} | {error, term()}.
As start_link/5, with start options (see "Transfer I/O" above).
-spec start_link_direct(module(), macula:pool(), <<_:256>>, macula:realm(), binary()) -> {ok, pid()} | {error, term()}.
As start_link/4, but resolves Station's own station_endpoint and dials it directly instead of putting through the pool's existing links. See the "Direct-dial" section above.
-spec start_link_direct(module(), macula:pool(), <<_:256>>, macula:realm(), binary(), term()) -> {ok, pid()} | {error, term()}.
As start_link_direct/5, with Args passed to Module:init/1.
-spec start_link_direct(module(), macula:pool(), macula_identity:pubkey(), macula:realm(), binary(), term(), start_opts()) -> {ok, pid()} | {error, term()}.
As start_link_direct/6, with start options (see "Transfer I/O" above).