nquic_protocol_streams_send (nquic v1.0.0)

View Source

Send-side stream drain engine and blocked/pending stream tracking.

Pure functions over #conn_state{} that turn buffered outbound stream bytes into STREAM frames and maintain the indices that keep the flush path O(1) in the number of idle streams.

The drain engine (drain_pending_sends/1, drain_round_robin/2, drain_streams/3, drain_streams_capped/4, drain_one_stream/4, drain_chunks/5, drain_chunks_loop/11, emit_lone_fin/5, peel_bytes/2,3) walks the pending_send_streams index, sizing each STREAM frame to fit one MTU-bounded short-header packet and capping the total bytes per call by the connection cwnd headroom (cwnd_budget/1). Frame sizing helpers are stream_frame_overhead/2 and stream_frame_size/3.

Readiness/blocked tracking (check_writable_1byte/2, is_writable/2, mark_blocked/2, clear_blocked/2, scan_blocked_streams/1, scan_blocked_stream/2) detects flow/CC writable-edge transitions, and the pending-send index (mark_pending_send/2, clear_pending_send/2, has_pending_send/1, sync_pending_send/2, put_stream/3) plus peer stream-id tracking (track_peer_stream_id/2) round out the send-side bookkeeping. Terminal reclamation and MAX_STREAMS auto-extension are delegated to nquic_protocol_streams_lifecycle.

Summary

Functions

Check that at least one byte can be sent on the given stream right now.

Remove StreamID from the pending-send index. Idempotent. Call after a drain empties the buffer (and any FIN has been emitted) or after cleanup_stream/reset_stream purge the buffer.

Walk all streams with buffered outbound data and produce STREAM frames. Each frame is sized to fit in one MTU-bounded short-header packet and is queued onto pending_app_frames. The per-connection cwnd headroom caps the total bytes drained per call: anything that doesn't fit stays in the stream's pending_send_data and is drained on the next flush (typically triggered when an ACK frees bytes_in_flight). Iterates the pending_send_streams index rather than the full streams map, so connections with many idle streams pay only for the ones with buffered bytes or a latched FIN.

Quick check: does any stream still have buffered outbound data or a latched FIN?

Public predicate: is this stream currently writable? Unknown stream IDs return false. Applies the lazy stream-limit initialisation used by send_stream/4 so freshly-opened streams report accurately before the first send. Used by nquic_lib:is_writable/2.

Add StreamID to the pending-send index. Idempotent. Call after any operation that grows pending_send_data or latches pending_send_fin on a live stream. Maintaining the index allows flush/1 and drain_pending_sends/1 to short-circuit in O(1).

Reconcile the pending-send index for StreamID against the stream's current pending_send_size / pending_send_fin. Use after operations that may have flipped membership in either direction.

Functions

check_writable_1byte/2

-spec check_writable_1byte(#stream_state{stream_id :: nquic:stream_id(),
                                         type :: bidi | uni,
                                         send_state ::
                                             ready | send | data_sent | data_recvd | reset_sent |
                                             reset_recvd,
                                         send_offset :: non_neg_integer(),
                                         send_max_data :: non_neg_integer(),
                                         last_stream_data_blocked :: non_neg_integer(),
                                         pending_send_data :: [binary()],
                                         pending_send_size :: non_neg_integer(),
                                         pending_send_fin :: boolean(),
                                         recv_state ::
                                             recv | size_known | data_recvd | reset_recvd | data_read |
                                             reset_read,
                                         recv_offset :: non_neg_integer(),
                                         recv_max_offset :: non_neg_integer(),
                                         recv_window :: non_neg_integer(),
                                         recv_buffer ::
                                             gb_trees:tree(non_neg_integer(), {binary(), boolean()}),
                                         app_buffer :: iodata(),
                                         app_buffer_size :: non_neg_integer()},
                           nquic_protocol:state()) ->
                              boolean().

Check that at least one byte can be sent on the given stream right now.

Runs the three send-side checks (connection flow, stream flow, congestion control) against a length of 1, gated on the stream's send state being still writable. Returns true iff all checks pass. Used to detect writable-edge transitions for previously flow/CC-blocked streams.

clear_blocked/2

clear_pending_send/2

-spec clear_pending_send(nquic:stream_id(), nquic_protocol:state()) -> nquic_protocol:state().

Remove StreamID from the pending-send index. Idempotent. Call after a drain empties the buffer (and any FIN has been emitted) or after cleanup_stream/reset_stream purge the buffer.

drain_pending_sends/1

-spec drain_pending_sends(nquic_protocol:state()) -> nquic_protocol:state().

Walk all streams with buffered outbound data and produce STREAM frames. Each frame is sized to fit in one MTU-bounded short-header packet and is queued onto pending_app_frames. The per-connection cwnd headroom caps the total bytes drained per call: anything that doesn't fit stays in the stream's pending_send_data and is drained on the next flush (typically triggered when an ACK frees bytes_in_flight). Iterates the pending_send_streams index rather than the full streams map, so connections with many idle streams pay only for the ones with buffered bytes or a latched FIN.

has_pending_send/1

-spec has_pending_send(nquic_protocol:state()) -> boolean().

Quick check: does any stream still have buffered outbound data or a latched FIN?

is_writable/2

-spec is_writable(nquic:stream_id(), nquic_protocol:state()) -> boolean().

Public predicate: is this stream currently writable? Unknown stream IDs return false. Applies the lazy stream-limit initialisation used by send_stream/4 so freshly-opened streams report accurately before the first send. Used by nquic_lib:is_writable/2.

mark_blocked/2

mark_pending_send/2

-spec mark_pending_send(nquic:stream_id(), nquic_protocol:state()) -> nquic_protocol:state().

Add StreamID to the pending-send index. Idempotent. Call after any operation that grows pending_send_data or latches pending_send_fin on a live stream. Maintaining the index allows flush/1 and drain_pending_sends/1 to short-circuit in O(1).

scan_blocked_stream/2

-spec scan_blocked_stream(nquic:stream_id(), nquic_protocol:state()) ->
                             {[nquic_protocol:event()], nquic_protocol:state()}.

scan_blocked_streams/1

-spec scan_blocked_streams(nquic_protocol:state()) -> {[nquic_protocol:event()], nquic_protocol:state()}.

stream_frame_overhead(StreamID, Offset)

-spec stream_frame_overhead(nquic:stream_id(), non_neg_integer()) -> pos_integer().

stream_frame_size(StreamID, Offset, Length)

-spec stream_frame_size(nquic:stream_id(), non_neg_integer(), non_neg_integer()) -> pos_integer().

sync_pending_send(StreamID, State)

-spec sync_pending_send(nquic:stream_id(), nquic_protocol:state()) -> nquic_protocol:state().

Reconcile the pending-send index for StreamID against the stream's current pending_send_size / pending_send_fin. Use after operations that may have flipped membership in either direction.

track_peer_stream_id/2

-spec track_peer_stream_id(nquic:stream_id(), nquic_protocol:state()) -> nquic_protocol:state().