quic_socket (quic v1.3.0)
View SourceUDP socket abstraction with packet batching support.
This module provides a unified socket interface that: - Uses the OTP 27+ socket module with GSO/GRO on Linux - Falls back to gen_udp on macOS/Windows/other platforms - Batches outgoing packets for improved throughput - Handles coalesced packets on receive (GRO)
Architecture
quic_connection/quic_listener
|
quic_socket (this module)
|
socket module (OTP 27+) with GSO/GRO on Linux
or
gen_udp fallback on macOS/WindowsConfiguration
quic:start_server(Name, Port, #{
batching => #{
enabled => true,
max_packets => 64
}
}).
Summary
Functions
Close the socket and flush any pending packets. Only closes the socket if owns_socket is true (i.e., socket was created by us).
Set the controlling process.
Detect platform capabilities for GSO/GRO.
Flush all buffered packets. On hard send error the batch is cleared in the returned state so callers thread a clean buffer; PTO-driven retransmission owns recovery of any packets the CC has already tracked.
Get the underlying file descriptor.
Get the underlying socket from a socket_state.
Check if GSO is supported for this socket_state.
Return a map describing the socket_state's configuration and observability counters. Intended for debugging, benchmarking, and test assertions.
Create a fresh per-connection sender that reuses an existing socket (e.g. the listener's shared UDP socket on the server side). Each caller gets its own batch buffer so multiple connections can accumulate packets independently before flush. GSO is inherited from the underlying backend when requested. The socket is NOT owned by the returned state - close/1 will not close it.
Open a UDP socket with batching support. Options: - All standard gen_udp options - batching => #{enabled => true, max_packets => 64, flush_timeout_ms => 1}
Open a UDP socket optimized for sending (client connections). Detects platform capabilities and enables GSO if available. Unlike open/2, this is optimized for a single destination.
Open a server send socket bound to a local address with reuseport. Uses OTP socket backend with GSO support on Linux for high throughput. This is for server connections that need to send from a specific local port.
Receive packets from the socket. On Linux with GRO, may return multiple coalesced packets.
Send a packet, buffering for batch send if enabled. Packet can be: - binary() - already flat packet - {iov, [binary()]} - packet parts for scatter/gather (avoids copy)
Send a packet immediately, bypassing the batch buffer. Intended for one-shot control-plane sends (version negotiation, retry, stateless reset) where batching adds no value and persisting the returned state is awkward.
Swap the underlying socket handle without rebuilding the #socket_state{}. Used by client-migration rebind so batching configuration is preserved while the handle points at a fresh ephemeral port. Callers must flush any pending batch before swapping; packets buffered under the old handle cannot migrate to the new one.
Set socket options.
Get the local address and port.
Spawn a client-side receiver process for the socket backend.
Stop a client receiver process previously returned by start_client_receiver/2. Safe to call with undefined.
Wrap an existing gen_udp socket with batching support. This allows adding batching to connections that already have a socket. Note: GSO/GRO are not available when wrapping existing gen_udp sockets. The wrapped socket is NOT owned by this state - close/1 will not close it.
Types
Functions
-spec close(socket_state()) -> ok.
Close the socket and flush any pending packets. Only closes the socket if owns_socket is true (i.e., socket was created by us).
-spec controlling_process(socket_state(), pid()) -> ok | {error, term()}.
Set the controlling process.
-spec detect_capabilities() -> map().
Detect platform capabilities for GSO/GRO.
-spec flush(socket_state()) -> {ok, socket_state()} | {error, term(), socket_state()}.
Flush all buffered packets. On hard send error the batch is cleared in the returned state so callers thread a clean buffer; PTO-driven retransmission owns recovery of any packets the CC has already tracked.
-spec get_fd(socket_state()) -> {ok, integer()} | {error, term()}.
Get the underlying file descriptor.
-spec get_socket(socket_state()) -> socket:socket() | gen_udp:socket().
Get the underlying socket from a socket_state.
-spec gso_supported(socket_state()) -> boolean().
Check if GSO is supported for this socket_state.
-spec info(socket_state()) -> #{backend := gen_udp | socket, gso_supported := boolean(), gso_size := non_neg_integer(), gro_enabled := boolean(), batching_enabled := boolean(), max_batch_packets := pos_integer(), batch_flushes := non_neg_integer(), packets_coalesced := non_neg_integer()}.
Return a map describing the socket_state's configuration and observability counters. Intended for debugging, benchmarking, and test assertions.
-spec new_sender(gen_udp:socket() | socket:socket(), map()) -> {ok, socket_state()}.
Create a fresh per-connection sender that reuses an existing socket (e.g. the listener's shared UDP socket on the server side). Each caller gets its own batch buffer so multiple connections can accumulate packets independently before flush. GSO is inherited from the underlying backend when requested. The socket is NOT owned by the returned state - close/1 will not close it.
-spec open(inet:port_number(), map()) -> {ok, socket_state()} | {error, term()}.
Open a UDP socket with batching support. Options: - All standard gen_udp options - batching => #{enabled => true, max_packets => 64, flush_timeout_ms => 1}
-spec open_for_send(inet:ip_address(), map()) -> {ok, socket_state()} | {error, term()}.
Open a UDP socket optimized for sending (client connections). Detects platform capabilities and enables GSO if available. Unlike open/2, this is optimized for a single destination.
-spec open_server_send({inet:ip_address(), inet:port_number()}, map()) -> {ok, socket_state()} | {error, term()}.
Open a server send socket bound to a local address with reuseport. Uses OTP socket backend with GSO support on Linux for high throughput. This is for server connections that need to send from a specific local port.
-spec recv(socket_state(), timeout()) -> {ok, {inet:ip_address(), inet:port_number()}, [binary()]} | {error, term()}.
Receive packets from the socket. On Linux with GRO, may return multiple coalesced packets.
-spec send(socket_state(), inet:ip_address(), inet:port_number(), packet_view()) -> {ok, socket_state()} | {error, term()} | {error, term(), socket_state()}.
Send a packet, buffering for batch send if enabled. Packet can be: - binary() - already flat packet - {iov, [binary()]} - packet parts for scatter/gather (avoids copy)
Returns updated state. Auto-flushes when: - Batch is full (max_batch_packets reached) - Destination address changes
-spec send_immediate(socket_state(), inet:ip_address(), inet:port_number(), packet_view()) -> {ok, socket_state()} | {error, term()}.
Send a packet immediately, bypassing the batch buffer. Intended for one-shot control-plane sends (version negotiation, retry, stateless reset) where batching adds no value and persisting the returned state is awkward.
-spec set_socket(socket_state(), gen_udp:socket() | socket:socket()) -> socket_state().
Swap the underlying socket handle without rebuilding the #socket_state{}. Used by client-migration rebind so batching configuration is preserved while the handle points at a fresh ephemeral port. Callers must flush any pending batch before swapping; packets buffered under the old handle cannot migrate to the new one.
-spec setopts(socket_state(), list()) -> ok | {error, term()}.
Set socket options.
-spec sockname(socket_state()) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, term()}.
Get the local address and port.
-spec start_client_receiver(socket_state(), pid()) -> {ok, pid()} | {error, term()}.
Spawn a client-side receiver process for the socket backend.
The OTP socket module does not support {active, N} semantics, so instead we spawn a dedicated process that loops on socket:recvfrom/4 and forwards each datagram as {udp, Owner, IP, Port, Data} to the owning connection process. This lets the existing quic_connection receive path keep pattern-matching on the gen_udp-style message shape without any branching.
Returns the receiver pid, linked to the caller so it terminates when the connection process exits.
-spec stop_client_receiver(pid() | undefined) -> ok.
Stop a client receiver process previously returned by start_client_receiver/2. Safe to call with undefined.
-spec wrap(gen_udp:socket(), map()) -> {ok, socket_state()}.
Wrap an existing gen_udp socket with batching support. This allows adding batching to connections that already have a socket. Note: GSO/GRO are not available when wrapping existing gen_udp sockets. The wrapped socket is NOT owned by this state - close/1 will not close it.