masque_upstream_owner (masque v0.7.0)

View Source

Per-connection owner for a pooled upstream transport conn.

One masque_upstream_owner gen_server wraps exactly one h2 / quic_h3 client connection. It is the process that called h2:connect/3 / quic_h3:connect/3, so it is the process both transport libraries deliver connection-level events to (response headers, h3 datagrams, connection close, SETTINGS updates). The owner hands per-stream events off to the calling session via the transport library's set_stream_handler/3,4 so {h2, _, {data, _, _, _}} / {quic_h3, _, {data, _, _, _}} messages flow to the session's mailbox directly. The owner only demuxes the things set_stream_handler does not cover: h3 datagrams (which the transport delivers to the connection owner), stream resets, and connection-close notifications.

Lifecycle:

  • Spawned by masque_upstream_pool via the pool dialer process, which has just completed the handshake and owns the conn. Ownership is transferred to this process on start.
  • Sessions call acquire_stream/4 to open a new tunnel on the pooled conn; the owner issues the CONNECT request, registers the session as the stream's handler, and returns {ok, StreamId, Conn}.
  • Sessions call release_stream/2 on normal or abnormal exit. The owner also monitors every session so a crashed session releases its slot automatically.
  • When the last stream releases, the owner arms an idle timer (default 30 s, override via idle_timeout_ms). Expiry -> close the conn and stop normally; the pool registry observes the DOWN and drops its cache entry.

h1 does not use this module: h1 is 1-tunnel-per-socket, so the pool bypasses it and sessions keep owning their socket directly.

Summary

Functions

Open a new tunnel stream on this pooled conn. Issues the CONNECT request synchronously and registers the session as the stream's handler so subsequent stream-data events flow directly to the session's mailbox. Returns the transport's conn pid too so the session can issue its own outbound calls (send_data, send_datagram, etc.).

Diagnostic snapshot.

Release the stream previously acquired by this session. Safe to call multiple times (second call is a no-op).

Spawn an owner that dials the upstream itself and becomes the transport connection's owner.

Types

start_args/0

-type start_args() ::
          #{transport := h2 | quic_h3,
            conn := pid(),
            transport_mod => module(),
            fingerprint => term(),
            idle_timeout_ms => non_neg_integer() | infinity,
            max_streams => pos_integer() | dynamic}.

Functions

acquire_stream(Owner, Headers, SessionPid, ReqOpts)

-spec acquire_stream(pid(), [{binary(), binary()}], pid(), map()) ->
                        {ok, non_neg_integer(), pid()} | {error, term()}.

Open a new tunnel stream on this pooled conn. Issues the CONNECT request synchronously and registers the session as the stream's handler so subsequent stream-data events flow directly to the session's mailbox. Returns the transport's conn pid too so the session can issue its own outbound calls (send_data, send_datagram, etc.).

code_change(OldVsn, S, Extra)

handle_call(Req, From, State)

handle_cast(Msg, S)

handle_info(Msg, State)

info(Owner)

-spec info(pid()) -> map().

Diagnostic snapshot.

init(Args)

release_stream(Owner, StreamId)

-spec release_stream(pid(), non_neg_integer()) -> ok.

Release the stream previously acquired by this session. Safe to call multiple times (second call is a no-op).

start_for_pool(RegistryPid, Tag, Opts)

-spec start_for_pool(pid(), term(), map()) -> pid().

Spawn an owner that dials the upstream itself and becomes the transport connection's owner.

The spawned process blocks on the handshake in its init; the transport's Conn pid is therefore parented to the owner (no ownership transfer required, which matters for quic_h3 where the public API does not expose a controlling_process/2 equivalent). On success it sends {dial_result, Tag, {ok, Self}} to RegistryPid and then enters the normal gen_server loop. On failure it sends {dial_result, Tag, {error, Reason}} and exits normally.

Opts must contain transport (h2 | quic_h3), host, port, and any transport-specific connect_opts. The optional transport_mod points at a mock implementing the same surface so unit tests can drive this path without a real TLS handshake.

start_link(Args)

-spec start_link(start_args()) -> {ok, pid()} | {error, term()}.

stop(Owner)

-spec stop(pid()) -> ok.

terminate(Reason, S)