Hot-path outbound RTP without GenServer hops on Peer or Listener.
Why a send handle exists
After DTLS, every media packet must be SRTP-encrypted and written to UDP
(or TURN-wrapped). Calling into the Peer GenServer for each packet adds
latency and mailbox pressure. Instead, Peer publishes encrypt state into an
ETS table; forwarders call send_rtp/3 with a handle from
XGW.Peer.outbound_handle/1.
ICE-TCP has no hot handle ({:error, :use_genserver}); fall back to
XGW.Peer.send_rtp_async/3.
Benches/tests may call publish_passthrough/2 (RTP) or
publish_dc_passthrough/2 (datachannel) to register a synchronous MFA sink
so Forwarder takes the SendHandle success path without a Peer GenServer hop.
Datachannel production egress uses the per-Peer XGW.Peer.Datachannel process
(Peer.send_datachannel_async/4 casts there when published). A full SCTP-in-ETS
hot path is not shipped (ssl_sock ownership).
ponytail: v1 assumes one packet writer per Peer outbound (forwarder). ROC
write-back uses generation-matched select_replace so Peer republish cannot
be clobbered. Multi-writer CAS across forwarders is the upgrade path.
RFCs
- RFC 3711 -- SRTP encrypt on the outbound hot path
- RFC 8656 -- TURN ChannelData / Send Indication when the path is relay
Summary
Types
Opaque handle used by send_rtp/3.
Functions
Hot-path send handle.
Creates the ETS table (idempotent). Called from XGW.Application.
Looks up a published handle for peer.
Removes any ETS row for peer.
Publishes a plaintext datachannel passthrough sink for benches/tests.
Publishes a plaintext passthrough sink for benches/tests (no SRTP/UDP).
Publishes or refreshes the hot-path row from Peer GenServer state.
Publishes RTP and datachannel passthrough MFAs in one ETS row so Forwarder can use both hot paths for the same peer pid (trunk pipe).
Hot-path datachannel send when a :dc_passthrough row is published.
Encrypts and sends one RTP packet on the hot path.
Types
@type t() :: %XGW.Peer.SendHandle{generation: pos_integer(), peer: pid()}
Opaque handle used by send_rtp/3.
Fields
:peer- Peer GenServer pid whose ETS row backs this handle:generation- publish generation; stale handles fail closed after republish
Functions
Hot-path send handle.
Fields
:peer- Peer GenServer pid whose ETS row backs this handle:generation- publish generation; stale handles fail closed after republish
@spec ensure_table!() :: :ok
Creates the ETS table (idempotent). Called from XGW.Application.
Looks up a published handle for peer.
Returns {:error, :not_ready} when missing, or {:error, :use_genserver} for
ICE-TCP / GenServer-only rows.
@spec invalidate(pid()) :: :ok
Removes any ETS row for peer.
Publishes a plaintext datachannel passthrough sink for benches/tests.
Distinct from RTP publish_passthrough/2 so MFA arity does not clash
(apply(mod, fun, [sink, channel_ref, data, type]) where type is
:string | :binary).
Publishes a plaintext passthrough sink for benches/tests (no SRTP/UDP).
Forwarder prefers send_rtp/3 when a handle exists. The MFA is invoked
synchronously as apply(mod, fun, [sink, ssrc, rtp]) so latency probes can
record without a Peer GenServer hop. Used by latency benches and FakePeer tests.
Publishes or refreshes the hot-path row from Peer GenServer state.
Preserves existing ETS encrypt contexts across path republish. TCP selected transport stores a GenServer-only marker (no UDP hot path).
@spec publish_trunk_passthrough( pid(), {module(), atom(), pid()}, {module(), atom(), pid()} ) :: :ok
Publishes RTP and datachannel passthrough MFAs in one ETS row so Forwarder can use both hot paths for the same peer pid (trunk pipe).
@spec send_datachannel(t() | pid(), reference(), binary(), :string | :binary) :: :ok | {:error, term()}
Hot-path datachannel send when a :dc_passthrough row is published.
type is :string (PPI 51) or :binary (PPI 53); passed to the MFA as the
fourth argument so benches can observe PPI. Production Peer fallback uses the
same type via XGW.Peer.send_datachannel_async/4.
Returns {:error, :not_ready} when missing, {:error, :stale} on generation
mismatch, or {:error, :use_genserver} for GenServer-only / RTP rows (caller
should fall back to XGW.Peer.send_datachannel_async/4).
@spec send_rtp(t() | pid(), non_neg_integer(), binary() | XMediaLib.Rtp.t()) :: :ok | {:error, term()}
Encrypts and sends one RTP packet on the hot path.
On success: no Peer or Listener GenServer involvement.