MuxServer
MuxServer is the server-side peer for MuxClient. It receives framed MUX
traffic on one parent transport line, creates child lines when Open frames
arrive, and forwards each child to next as a normal independent WaterWall
line.
The previous side must provide the transport line that carries MuxClient
frames. The next side receives the recreated logical child lines.
Typical Placement
Simple TCP transport:
client side: TcpListener -> MuxClient -> TcpConnector
server side: TcpListener -> MuxServer -> TcpConnector
With an outer protocol stack:
client side: TcpListener -> MuxClient -> HttpClient -> TlsClient -> TcpConnector
server side: TcpListener -> TlsServer -> HttpServer -> MuxServer -> TcpConnector
From the next node's point of view, each child created by MuxServer behaves
like a separate WaterWall connection.
What It Does
- Initializes a parent line when upstream
Initarrives from the transport. - Reads MUX frames from the parent line.
- Creates one child line per valid
Openframe. - Initializes each child line toward
next. - Routes
Data,Close,FlowPause, andFlowResumebycid. - Wraps downstream child payloads back into MUX
Dataframes. - Sends child finish events back to
MuxClientasCloseframes. - Queues parent-delivered data when a child's write side is paused.
- Detaches accepted child queues when the parent transport finishes and drains them under child backpressure.
MuxServer creates the child line_t objects behind the parent and is
responsible for destroying them. It does not create the parent transport line.
Configuration Example
{
"name": "mux-server",
"type": "MuxServer",
"settings": {
"child-buffer-limit": 134217728,
"child-buffer-resume-threshold": 524288,
"parent-buffer-limit": 536870912,
"max-children": 10000,
"max-live-children": 0,
"initial-child-idle-timeout-ms": 10000,
"active-child-idle-timeout-ms": 300000,
"memory-high-watermark-percent": 85,
"memory-low-watermark-percent": 80,
"memory-reserve": 134217728,
"memory-fallback-max-live-children": 60000,
"detached-buffer-limit": 268435456,
"detached-child-limit": 12000,
"log-main-line-stats": false
},
"next": "service-side-node"
}
Required Fields
Top-level fields:
| Field | Type | Description |
|---|---|---|
name | string | User-chosen node name. Must be unique inside the config file. |
type | string | Must be exactly "MuxServer". |
settings | object | Optional. Defaults are used when omitted. |
next | string | Required. The next node receives recreated child lines. |
MuxServer should have a previous transport-facing node and should be reached
by a matching MuxClient.
Parent keepalive replies
keepalive is an optional boolean setting, default true. MuxServer answers
complete empty Pings with empty Pongs carrying the same 32-bit token in the CID
field. These parent controls never create or look up children, or refresh child
idle deadlines. MuxServer starts no probes or health timer. Set the probe interval
and tolerance on MuxClient (defaults: 15000 ms and 45000 ms).
"keepalive": false consumes these controls without sending replies. MuxClient
requires a matching Pong to confirm support before enforcing timeouts, so a
disabled responder does not cause repeated replacements. Pongs wait during parent
output Pause and share the ordinary FIFO and parent write charge limit. Resume
preserves their token order and their order relative to application frames. A
nonempty Ping closes its parent through normal protocol-error cleanup.
Parent write buffering
Each parent has a lazy FIFO of encoded outgoing buffers, shared by its children.
The following optional settings measure queue-capacity charge in bytes:
sizeof(sbuf_t) + sbufGetTotalCapacity(buf) + kSbufAllocationAlignment.
Ordinary capacity is resident allocation; splice capacity is logical geometry.
Padding is included once. Kernel pipe capacity and control storage are not added;
this is not a physical memory, descriptor, or RSS limit.
| Setting | Default | Meaning |
|---|---|---|
parent-write-buffer-pause-threshold | 134217728 (128 MiB) | Pause attached child producers when retained charge reaches this value. |
parent-write-buffer-resume-threshold | 4194304 (4 MiB) | Release parent pressure at or below this charge while transport is writable. |
parent-write-buffer-limit | 536870912 (512 MiB) | Maximum retained charge for each parent; equality is allowed. |
Pause and hard limits must be integers in [1, INT_MAX]; resume accepts
[0, INT_MAX]. The effective tuple must satisfy 0 <= resume < pause < hard.
Omitted settings use their defaults. Resume defaults to 4194304 (4 MiB)
independently of pause. Explicit values are honored; 0 requires an empty
outgoing queue before release. Transport must be writable in every case. Booleans,
strings, null, fractions, negatives and out-of-range values reject startup. Invalid
combinations report all three effective values without clamping. For example,
inside settings (explicitly selects a 1.5 MiB resume threshold):
{
"parent-write-buffer-pause-threshold": 2097152,
"parent-write-buffer-resume-threshold": 1572864,
"parent-write-buffer-limit": 4194304
}
The gate uses retained queue charge (sbufGetQueueCharge), including header,
capacity, padding and alignment, rather than wire length. Resume runs before the
next FIFO pop, after the prior delivery returns. Synchronous resumed output
appends behind existing frames and complete splice batches. A new pause stops
an interrupted Resume pass; the FIFO of locally held children preserves later
release opportunities. Init/Est completion reconciles deferred producer permission.
Peer FlowPause and terminal close remain independent reasons to stop a source.
The default releases held producers when outgoing queue charge is at or below 4 MiB and the transport is writable. The 512 MiB limit is per parent, allocated on demand, and is not an RSS, socket-buffer or FD bound. Already-admitted Data can still reach a peer-paused child; the default 128 MiB child and 512 MiB parent receive budgets can shed children or close that parent.
With log-main-line-stats, five-second samples include
parent-output-queued-bytes, parent-output-queue-charge,
parent-output-queue-items, parent-transport-paused,
parent-sources-throttled, children-parent-write-paused,
children-peer-flow-paused, parent-output-throttle-ms, and
parent-output-last-throttle-ms, plus the three effective settings.
children-parent-write-paused counts individual writer and aggregate holds.
It can be nonzero while parent-sources-throttled is false. That boolean and
its current/last throttle durations describe only the aggregate pause-all latch.
Throttle durations use the owner's 64-bit monotonic clock; current duration is
zero outside a throttle episode. parent-child-queue-charge and
parent-input-queue-charge describe incoming storage. Logging remains optional
and has no role in flow-control progress.
These settings apply independently to every parent of this node. They are separate
from parent-buffer-limit, which bounds incoming assembly plus attached child queues.
Optional Settings
| Setting | Type | Default | Description |
|---|---|---|---|
child-buffer-limit | integer | 134217728 | Maximum queue-capacity charge for one paused child. Ordinary allocation is charged; splice uses logical sbuf capacity plus sbuf/alignment overhead. Must be greater than 0; reaching it closes the child with a MUX Close frame. |
child-buffer-resume-threshold | integer | 524288 | Logical queued-payload low-water mark for sending FlowResume after the local child becomes writable. Must be greater than 0. Its numeric value is capped to child-buffer-limit. Raising it resumes the peer earlier but weakens hysteresis and may increase pause/resume cycling. |
parent-buffer-limit | integer | 536870912 | Per-parent queue-capacity budget for incoming assembly plus attached child queues. Beneficial compaction precedes largest-child/oldest-tie shedding; unrelieved pressure closes this parent. Set to 0 to disable the aggregate budget. |
max-children | integer | 10000 | Hard maximum attached live children on one parent. Must be positive and, when max-live-children is nonzero, no greater than that limit; historical destroyed CIDs do not count. |
max-live-children | integer | 0 | Hard maximum reserved or initialized children across this MuxServer instance, all parents, and all workers. 0 disables this aggregate count ceiling; memory pressure, per-parent limits, and the memory-snapshot fallback ceiling remain active. Draining and detached children count until their MuxServer state is destroyed. |
initial-child-idle-timeout-ms | integer | 10000 | Idle timeout after Open and before the first nonempty Data payload. Must be positive. |
active-child-idle-timeout-ms | integer | 300000 | Refreshing idle timeout after nonempty payload activity begins. Must be positive and at least the initial timeout. |
memory-high-watermark-percent | integer | 85 | Close memory admission at or above this host or finite-cgroup usage. Must be in [1, 99]. |
memory-low-watermark-percent | integer | 80 | Reopen only when every applicable usage is at or below this value and reserve headroom recovered. Must be in [1, 99] and lower than the high watermark. |
memory-reserve | integer | By ram-profile | Close admission when effective available bytes reach this reserve. Must be in [0, INT_MAX]; 0 disables only this byte reserve. |
memory-fallback-max-live-children | integer | By ram-profile | Conservative aggregate ceiling while memory sampling is unsupported, unavailable, or older than one second, including when max-live-children is 0. Must be positive and, when max-live-children is nonzero, no greater than that limit. |
detached-buffer-limit | integer | By ram-profile | Per-worker queue-capacity-charge limit for blocked child queues kept after parent loss. Reaching it aborts only the newly detached blocked child. Set to 0 to disable this bound. |
detached-child-limit | integer | By ram-profile | Per-worker count limit for blocked children retained after parent loss. Reaching it aborts only the newly detached blocked child. Set to 0 to disable this bound. |
log-main-line-stats | boolean | false | When enabled, parent lines log best-effort stats every 5000 ms. Parent logical death suppresses normal execution and settles the task as LineDead when its queued/timed runner is reached. Worker quiescence actively cancels pending work. Neither path rearms it. |
The two detached limits use these defaults when their node settings are omitted:
| RAM profile | Default detached charge | Default detached children |
|---|---|---|
S1 (minimal / ultralow) | 33554432 (32 MiB) | 4096 |
| S2 | 80740352 (77 MiB) | 5677 |
M1 (client) | 127926272 (122 MiB) | 7258 |
M2 (client-larger) | 174063616 (166 MiB) | 8838 |
| L1 | 221249536 (211 MiB) | 10419 |
L2 (server, the global default) | 268435456 (256 MiB) | 12000 |
Defaults are linearly interpolated over the six ordered RAM-profile tiers, with the charge limit rounded to the nearest whole MiB. An explicit setting overrides the profile-derived value independently for that limit.
Memory admission has separate defaults for memory-reserve and
memory-fallback-max-live-children, shared across all parents and workers
of one MuxServer instance. Explicit settings override these defaults independently:
| RAM profile | Default memory reserve | Default fallback live children |
|---|---|---|
S1 (minimal / ultralow) | 16777216 (16 MiB) | 20480 |
| S2 | 40370176 (38.5 MiB) | 28385 |
M1 (client) | 63963136 (61 MiB) | 36290 |
M2 (client-larger) | 87031808 (83 MiB) | 44190 |
| L1 | 110624768 (105.5 MiB) | 52095 |
L2 (server, the global default) | 134217728 (128 MiB) | 60000 |
Pause tolerance and resume threshold intentionally continue to measure logical application payload bytes. Allocation charge is specialized to the three memory-sensitive hard byte budgets.
Stats include worker id, parent write-pause state, aggregate retained queue charge, child
count, children waiting for ordered Close completion, child read-pause count,
and child write-pause count. The legacy parent-line-read-paused field remains
present as no for compatibility. parent-child-queue-charge and
parent-input-queue-charge report separate attached-child and incoming capacity
charges; their sum is limited by parent-buffer-limit.
Mode settings such as timer, counter, and fixed-connections-count belong
only to MuxClient. MuxServer follows the frames it receives and does not
care which client-side mode created them.
Parent And Child Model
MuxServer has two line roles:
| Role | Meaning |
|---|---|
| parent line | The shared transport line received from the previous node. |
| child line | A normal line created by MuxServer for one cid from the parent. |
When an Open frame arrives:
MuxServerchecks whether thecidalready exists.- If the
cidis new, it creates a child line on the same worker. - It initializes MUX line state for that child.
- It links the child to the parent.
- It sends upstream
Inittonexton the child line.
Parent-to-child dispatch uses a hash index, so lookup and duplicate detection
remain average O(1). A duplicate Open for any indexed child, including a
peer-close drain, is a protocol violation and closes that parent.
Before allocation, a fresh Open must pass the per-parent hard limit, any
configured per-instance count ceiling, and cached memory admission. Aggregate
reservations remain counted when max-live-children is 0. Resource
rejection sends Close(cid) without creating a child and preserves the parent
and siblings. A parent has a 1024-rejection burst bucket refilled at 64 tokens
per second using the owner's monotonic clock; sustained rejected-Open abuse
closes only that parent.
The global sampler publishes memory every 500 ms and snapshots expire after
one second. On Linux, cgroup pressure is the maximum used percentage and the
minimum remaining allowance across every visible finite cgroup v2/v1 level;
MemAvailable still bounds effective headroom. A fully visible, verified cgroup
hierarchy with no finite limits uses fresh host RAM readings. Ancestor limits
are rechecked on every sample. Ambiguous, hidden, or unreadable cgroup
constraints use the conservative fallback. Windows uses available physical
memory. Workers consume only the cached atomic tuple, and the high/low
watermarks provide hysteresis. Stale or unsupported data uses the profile
fallback ceiling and cannot reopen a gate previously closed by fresh pressure.
Memory admission affects new Opens only. With fresh memory data,
max-live-children: 0 removes the aggregate child-count ceiling while keeping
memory admission and max-children in force; it does not disable the fallback
ceiling when fresh data is unavailable.
Frame Format
MuxServer expects the same eight-byte wire header used by MuxClient:
| Offset | Size | Meaning |
|---|---|---|
| 0 | 3 bytes | Unsigned payload length, big-endian. |
| 3 | 1 byte | Frame type/flags. |
| 4 | 4 bytes | Unsigned connection ID, big-endian. |
The inclusive payload maximum is 1 MiB (1,048,576 bytes) for every frame type, independent of RAM profile, buffer sizes and requested or granted pipe capacity. A maximum frame occupies 1,048,584 wire bytes; a preceding Open adds another eight bytes (1,048,592 total). Decoding rejects a larger declaration as soon as all eight header bytes arrive, even for unknown CIDs or types, and closes only that parent through normal parent-loss cleanup. After a valid Data header, the decoder delivers available body bytes incrementally; it does not wait for the whole frame. Data for an unknown or closed CID is discarded incrementally. Empty Data is consumed without a child Payload callback. Other frame bodies are also consumed incrementally, but control effects wait until their declared bodies end. Available body bytes and completed controls drain before retained incomplete input is checked against 1,048,584 bytes, including its cached header. Bytes not yet received do not count as retained input.
Frame flags:
| Flag | Meaning |
|---|---|
0 | Open |
1 | Close |
2 | FlowPause |
3 | FlowResume |
4 | Data |
5 | Ping: empty parent control; CID is the probe token |
6 | Pong: empty parent control; CID echoes the probe token |
One MUX Data frame carries at most 1,048,576 payload bytes. The shared encoder
splits a larger downstream child payload into consecutive Data frames in byte
order. The complete encoding, including all headers, must fit in uint32_t;
an unrepresentable encoding recycles the input and closes only that child.
The receiver does not restore the original callback boundary after this split.
Direction Behavior
| Incoming callback | Behavior |
|---|---|
parent upstream Init | Initializes parent state, optionally schedules stats logging, and reports downstream Est to the previous node. |
parent upstream Payload | Parses MUX frames from MuxClient; creates children for Open; routes Data, Close, FlowPause, and FlowResume by cid. |
parent upstream Pause / Resume | Stops all parent output immediately / drains its FIFO. Blocked writers receive local holds; the retained-charge threshold additionally pauses all children. |
parent upstream Finish | Destroys parent state immediately, transfers accepted child queues to child-only drains, and finishes each child after its queue becomes empty and writable. |
child downstream Payload | Prepends a MUX Data header and sends the frame back on the parent toward the previous node. |
child downstream Pause / Resume | Sends FlowPause / flushes queued data and possibly sends FlowResume. |
child downstream Finish | Detaches and destroys the owned child, then submits its encoded Close to ordered parent output. |
upstream Est | Disabled; reaching this callback is fatal. |
downstream Init | Disabled; reaching this callback is fatal. |
Frames for unknown cids and unknown frame kinds are dropped.
Child Idle Reclamation
Every admitted child starts with initial-child-idle-timeout-ms. The first
nonempty incoming Data chunk or nonempty payload produced by next promotes
it to active-child-idle-timeout-ms; every later nonempty chunk or payload
refreshes the active deadline. Open, Close, flow control, Init/Est, and zero-length Data do not
count as activity. Zero-length Data is consumed without a child Payload callback
or child queue entry.
Expiry closes only that child, emits Close(cid) while its parent is available,
and preserves the parent and siblings. Detached expiry uses the detached-abort
path. Explicit close removes the timer item immediately, so timer memory follows
live children rather than historical churn.
These limits and timers do not change MUX wire bytes. A parent stays reusable for its full transport lifetime, and destroying a child makes its count capacity available again on that same parent and MuxServer instance.
Backpressure And Queues
Parent transport Pause stops parent-bound Payload, including Open, Close,
flow controls and Ping/Pong. Admitted frames share one FIFO and charge limit;
Resume continues their original order. A child submitting Data while transport is
blocked acquires an individual local producer hold after its complete payload or
splice batch is admitted. A direct submission that leaves transport paused also
holds its writer before returning. A transient Pause followed by Resume uses the
final state. Empty Data follows the same policy; control frames alone do not make
the referenced child a writer. Idle siblings below the aggregate threshold remain
unpaused until they write.
At charge greater than or equal to parent-write-buffer-pause-threshold, the
aggregate pause-all latch still holds every attached open child. Local writer
holds do not activate that latch themselves. Both causes share one local reason
per child and one parent-local FIFO of held children; duplicate causes preserve
the child's place. A resumed writer that becomes held again joins the tail.
The pump releases local holds whenever the parent is alive, not finishing, not
quiescing, writable, and at or below parent-write-buffer-resume-threshold, even
if the aggregate latch never activated. It clears that latch under the same
condition. Before each release and the next FIFO pop it checks the state left by
the previous callback. A transport Pause, charge above low water, renewed
aggregate gate, parent loss, or quiescence interrupts release. Writable output
continues draining and reconsiders waiting children without requiring an extra
transport event. Peer FlowPause and terminal-close reasons remain independent.
Release snapshots reference every locally held line before the first callback; callbacks may destroy siblings or the parent. Only surviving, attached, open, non-starting children are notified. Init/Est records reasons separately from already-emitted producer permission, then reconciles once safe. Unlink, terminal transition and shutdown remove registry membership before state destruction and never emit Resume just to clear the registry. The registry holds no persistent line references. Reentrant output follows existing Data, controls and admitted splice-batch remainders. Incoming parent reads and unrelated parents remain active.
A candidate that would exceed parent-write-buffer-limit, or a queue reservation
failure, closes only the affected parent through normal local teardown. The
candidate and undeliverable output are recycled; the process is not terminated.
An unblocked direct write needs no FIFO allocation and is not constrained by the
retention limit. Small control frames use a fitting small pooled allocation with
chain padding. Parent loss immediately discards outgoing backlog, while existing
incoming child queues retain their separate detached-drain behavior.
MuxServer borrows its parent lines: local overflow destroys its parent state and notifies the parent owner, while MuxServer itself closes its owned children. Rejected-Open Close replies use the same FIFO as Data and other controls.
MUX keeps backpressure per child:
- If the service-side child pauses,
MuxServersendsFlowPausefor thatcid. - If peer
FlowPausearrives from the parent transport, reads from the service-side child are paused. - If parent-delivered data arrives for a paused child, data is queued on that child.
FlowPauseis sent by the child pause callback immediately, without waiting for queued payload to reach a threshold.- Once logical queued payload drops below
child-buffer-resume-threshold,FlowResumecan be sent. The configured value is capped tochild-buffer-limit. - Queue pressure never pauses reads on the shared parent transport, so one blocked destination cannot stop unrelated child frames from being demultiplexed.
- If one child's retained queue charge reaches
child-buffer-limit, the child is closed. - Reaching
parent-buffer-limitcounts incoming assembly plus attached child queues. Beneficial incoming compaction runs first, then largest-child/oldest-tie shedding. Several victims may be needed; unrelieved pressure closes the affected parent. - Setting
parent-buffer-limitto0disables only the aggregate budget; the per-child limit still applies.
Logical queue length remains the content-byte measure used for FIFO and flow control. The queue-capacity charge is a policy budget rather than whole-process RSS: allocator caches, queue-ring storage, and fixed pool baseline memory are outside an individual queue's charge.
This isolates a slow child and bounds retained queue capacity without creating parent-wide head-of-line blocking.
Finish And Close Handling
If MuxServer receives a Close frame for a child, the Close remains ordered
after every earlier queued Data byte. A paused child remains addressable by
cid; later frames for that closed cid are discarded, Resume continues the
FIFO drain, and Finish plus owned-line destruction happen only at the
empty-and-writable barrier.
During normal operation, if the service-facing side closes a child, MuxServer
encodes its Close, detaches and destroys the child state and owned line, then
submits the buffer to ordered parent output. A paused parent retains that Close
behind earlier output without retaining the child.
If the parent transport closes, MuxServer destroys the borrowed parent's MUX
state immediately and transfers each accepted child queue into a worker-local
owned registry. Writable queues finish immediately; paused queues drain on
later Resume without retaining or dereferencing the dead parent. New outbound
child data is rejected. detached-buffer-limit bounds residual retained resource
charge and detached-child-limit bounds residual child count per worker.
Buffer And Padding Notes
MuxServer prepends MUX headers to downstream child payloads. Node metadata
advertises:
required_padding_left = 8
layer_group = kNodeLayer4
layer_group_next_node = kNodeLayer4
layer_group_prev_node = kNodeLayer4
Incoming parent bytes wait only for a complete header or more body bytes.
Available Data passes incrementally to the child or its bounded queue; other body
bytes are consumed without delivery. After draining available input, if the
retained incomplete stream grows above 1,048,584 bytes, MuxServer discards
the incomplete remainder, finishes the parent toward the previous node, and
uses the same detached drain for already parsed child queues.
Practical Notes
- Pair this node with
MuxClient; it is not useful by itself. MuxServerhas no client-side concurrency mode settings.- A
TcpConnectorafterMuxServercreates one service connection per child. MuxServeris a middle tunnel, not a generic endpoint.- A service-side Finish or orderly worker shutdown may discard residual detached data. Worker shutdown forwards no queued Payload, even for a writable child, before closing every remaining owned child.
- No MUX wire change or peer capability is introduced.
Worker shutdown
During worker quiescence, MuxServer detaches its idle timer and switches terminal cleanup to discard retained
MUX queues without sending payload, Close frames, or flow-control work. Its worker-local child idle table
inventories every owned child, attached or detached. Worker stop drains the complete table, releases each child’s
queue charge and live reservation exactly once, finishes toward the child destination, and destroys the child.
A borrowed parent can remain alive with valid MUX state and outgoing backlog until its actual owner sends Finish later. The idle
table is destroyed on its worker after drain; aggregate live-child checks run after all workers have stopped.
Ordinary connection loss, idle expiry, and ordered peer Close keep their normal runtime behavior.
Byte streams and UDP
Each child is an ordered byte stream. Neither the original Payload callback boundary nor the MUX Data frame boundary is part of the child contract. A frame may produce several nonempty child Payload callbacks as its body arrives. For example, a frame declaring 64 KiB can deliver its first available 4 KiB without waiting for the other 60 KiB. Later frames on the same parent still follow the current frame's remaining bytes.
The parent receive accumulator caches only the eight-byte header. It tracks the remaining Data body length, extracts available chunks with independent ownership, and gathers the next header only after the current body is consumed. Paused children retain those chunks in FIFO order under the existing queue budgets. A child that closes during delivery receives no more bytes; the parser discards that frame's remaining body before interpreting another header. Already delivered prefixes stay delivered if the parent fails. Accepted queued chunks retain the normal detached-drain behavior, even when the frame's suffix never arrived.
Zero-length child payloads still encode as a Data header with no body. The receiver consumes that frame without creating a child Payload callback or queue entry; it means neither EOF nor Close and does not refresh child idle activity.
For UDP traffic, use explicit UdpOverTcpClient/UdpOverTcpServer framing on the
child sides of MUX:
client: UdpListener -> UdpOverTcpClient -> MuxClient -> TcpConnector
server: TcpListener -> MuxServer -> UdpOverTcpServer -> UdpConnector
Direct UDP adapters next to MUX cannot rely on datagram boundaries. The UdpOverTcp pair currently requires nonempty datagrams; MUX stream delivery does not provide a representation for an empty datagram.
Splice retention and large batches
See the developer guide for the limits table, allocation and data-flow walkthrough, and edge-case reference.
Mux uses one queue-capacity charge for every owned buffer:
sizeof(sbuf_t) + sbufGetTotalCapacity(buf) + kSbufAllocationAlignment
Capacity includes reserved left padding. Ordinary entries therefore retain their allocation-based charge; splice entries charge logical sbuf capacity, without adding wrapper control storage or kernel pipe capacity. Unknown cached pipe capacity does not force a valid retained source into ordinary storage.
parent-buffer-limit (512 MiB by default) covers incoming frame assembly plus
all attached child queues. The stream counter remains separate from the child
aggregate, and their sum is checked at stable return boundaries. Available Data
chunks and complete controls in a coalesced delivery drain first, even if its
temporary charge exceeds the limit. A paused child retains chunks as they arrive,
so one Data frame may create several charged queue entries. Cached header bytes
count toward the independent 1,048,584-byte incomplete-input bound until the body
ends, but the header cache and stream/queue metadata carry no sbuf charge.
The declared body bytes still missing from the transport carry no retention charge.
Active splice-head body consumption reduces logical capacity and its charge;
prefix consumption and ordinary cursor movement do not reduce capacity.
Reaching a finite receive limit first attempts to combine incoming fragments into one ordinary best-fit buffer, only if its predicted total charge is strictly lower. Compaction preserves the cached header and exact byte order. It does not run on every Push or rewrite child/output queues. Remaining pressure sheds the largest queued child, preferring the oldest on ties, and rechecks after every callback. Several victims may be needed. No victim or no progress closes only that parent through normal parent-loss cleanup. A zero parent limit disables this combined finite bound; it introduces no other aggregate cap.
child-buffer-limit remains 128 MiB by default and rejects equality. Outgoing
parent queues remain separate: their default pause threshold is 128 MiB, resume
threshold is 4 MiB, and hard limit is 512 MiB; hard-limit equality is permitted.
Detached queues retain their per-worker settings and zero/unlimited meanings.
The FlowResume threshold counts
logical payload bytes; FlowPause follows the local child pause state. Ordinary
child candidates may still compact to a cheaper pooled tier; valid splice
candidates are not individually materialized for queue pressure.
There is no worker-wide pipe-count or pipe-capacity quota. Other parents cannot force fallback through shared pipe reservations. These charges are not physical memory, FD, kernel page-reference, socket-queue or RSS bounds, nor a strict instantaneous memory ceiling. More retained splice data can use more descriptors and nominal kernel capacity under the same logical budget. Pools may cache empty pipes after recycling; lower charge does not prove those resources were closed.
Inputs through 1 MiB use the fitting single-buffer path, including one Data header for empty input. Larger ordinary input uses one encoded aggregate; larger splice input uses a complete ordered batch, with Open once before the first client Data. A real 1 MiB pipe body plus a resident prefix can require splitting. Batch staging uses completed logical capacity for pipe estimates and reserves ordinary fallback costs for the remaining frames. All fallible admission precedes publication and callbacks. Refusal discards the local batch and closes only the affected parent. A fitting writable-parent direct write still needs no FIFO admission.
Actual pipe creation, capacity and slot pressure remain transfer constraints. Short progress/EINTR and complete ordinary fallback preserve every byte, even after partial transfer. Nominal 1 MiB capacity does not prove available slots. Best-fit fallback preserves headroom and may exceed a low-profile large tier. Pool sizes, pipe targets, protocol limits and JSON defaults remain unchanged.
Pause stops output, including Ping/Pong; Resume preserves the shared FIFO. Nested application output and child Close follow admitted Data. Child death does not release parent-owned output. Parent loss discards incoming/output ownership and transfers eligible blocked child queues to detached accounting. Pop, transfer and discard settle scalar charges before callbacks; final owner cleanup settles every queue. HTTP and ordinary BufferStream remain ordinary-only.
Node Metadata
Source-backed metadata:
| Property | Value |
|---|---|
| node flags | kNodeFlagSupportsSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 8 bytes |