# Stream-Level vs Connection-Level Flow Control in XQUIC: Implementation Guide

> Master XQUIC flow control. Learn the crucial differences between stream-level and connection-level flow control implementations for better network performance. Get the guide now.

- Repository: [Alibaba/xquic](https://github.com/alibaba/xquic)
- Tags: how-to-guide
- Published: 2026-02-24

---

**Stream-level flow control limits the amount of data that can be sent on a single QUIC stream, while connection-level flow control limits the total data across all streams combined.**

XQUIC, Alibaba's open-source QUIC protocol implementation, employs a two-tier flow control mechanism that follows the IETF QUIC specification. Understanding the distinction between these limits is essential for debugging performance bottlenecks and optimizing data transmission in multiplexed connections. This guide examines the source code in `alibaba/xquic` to explain how these limits are enforced, updated, and handled when exceeded.

## What is Connection-Level Flow Control?

Connection-level flow control manages the aggregate data flow across the entire QUIC connection. It prevents a peer from flooding the link and exhausting the receiver's buffers globally by capping the total bytes in flight on all streams combined.

The state is tracked in the **`xqc_conn_flow_ctl_t`** structure (defined in [`include/xquic/xqc_typedef.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xqc_typedef.h)). Key fields include:

- `fc_max_data_can_send` – The maximum bytes the local endpoint may transmit
- `fc_data_sent` – Total bytes already sent
- `fc_max_data_can_recv` – The limit advertised by the peer
- `fc_data_recved` – Total bytes received

When emitting packets, XQUIC validates the connection limit in [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c) before transmission:

```c
/* Lines 351-359 in xqc_stream.c */
if (stream->stream_conn->conn_flow_ctl.fc_data_sent + stream->stream_conn->pkt_out_size >
    stream->stream_conn->conn_flow_ctl.fc_max_data_can_send) {
    /* Exceeds connection window → block */
    xqc_write_data_blocked_to_packet(conn, conn->conn_flow_ctl.fc_max_data_can_send);
}

```

## What is Stream-Level Flow Control?

Stream-level flow control provides granular back-pressure for individual streams. It ensures no single stream monopolizes available bandwidth, maintaining fairness across multiple concurrent transfers within the same connection.

The **`xqc_stream_flow_ctl_t`** structure tracks per-stream limits:

- `fc_max_stream_data_can_send` – Bytes allowed on this specific stream
- `fc_max_stream_data_can_recv` – Peer-advertised limit for this stream
- `fc_stream_recv_window_size` – Current receive window allocation

Before writing data to a specific stream, the implementation checks the per-stream constraint:

```c
/* Lines 364-370 in xqc_stream.c */
if (stream->stream_send_offset + stream->stream_conn->pkt_out_size >
    stream->stream_flow_ctl.fc_max_stream_data_can_send) {
    /* Exceeds stream window → block */
    xqc_write_max_stream_data_to_packet(conn, stream->stream_id,
        stream->stream_flow_ctl.fc_max_stream_data_can_send, XQC_PTYPE_SHORT_HEADER);
}

```

## Key Differences Between Stream and Connection Flow Control

While both mechanisms prevent buffer overflow, they operate at different granularities within the XQUIC architecture:

**Scope of Limitation**
- **Connection-level**: Governs total bytes across all bidirectional and unidirectional streams combined
- **Stream-level**: Governs bytes sent on a single stream ID only

**Transport Frames**
- **`MAX_DATA`** frames advertise connection-wide limits
- **`MAX_STREAM_DATA`** frames advertise per-stream limits

**Error Handling**
When limits are exceeded, XQUIC generates distinct errors defined in [`include/xquic/xqc_errno.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xqc_errno.h):
- **`XQC_ECONN_BLOCKED`** (line 101): Generated when the connection cannot send more data
- **`XQC_ESTREAM_BLOCKED`** (line 102): Generated when a specific stream cannot send more data

## Implementation in XQUIC Source Code

### Initializing Flow Control Limits

Connection limits are established during the handshake via `xqc_conn_init_flow_ctl` in [`src/transport/xqc_conn.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_conn.c) (lines 598-604):

```c
flow_ctl->fc_max_data_can_send = settings->max_data;   /* Advertised to peer */
flow_ctl->fc_max_data_can_recv = settings->max_data;   /* Peer-advertised limit */

```

Per-stream limits are configured when opening streams via `xqc_stream_open` in [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c) (lines 246-274). For bidirectional client streams, the implementation maps the appropriate transport parameters:

```c
if (type == XQC_CLI_BID) {
    stream->stream_flow_ctl.fc_max_stream_data_can_send = remote_settings->max_stream_data_bidi_remote;
    stream->stream_flow_ctl.fc_max_stream_data_can_recv = local_settings->max_stream_data_bidi_local;
    stream->stream_flow_ctl.fc_stream_recv_window_size = local_settings->max_stream_data_bidi_local;
}

```

### Processing Window Updates

When receiving `MAX_DATA` frames, XQUIC updates the connection receive window in [`src/transport/xqc_frame.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_frame.c):

```c
/* Lines 600-610 in xqc_frame.c */
if (new_limit > conn->conn_flow_ctl.fc_max_data_can_recv) {
    conn->conn_flow_ctl.fc_max_data_can_recv = new_limit;
    xqc_write_max_data_to_packet(conn, conn->conn_flow_ctl.fc_max_data_can_recv);
}

```

Similarly, `MAX_STREAM_DATA` frames trigger updates to specific streams:

```c
/* Lines 608-610 in xqc_frame.c */
if (new_limit > stream->stream_flow_ctl.fc_max_stream_data_can_recv) {
    stream->stream_flow_ctl.fc_max_stream_data_can_recv = new_limit;
    xqc_write_max_stream_data_to_packet(conn, stream->stream_id,
        stream->stream_flow_ctl.fc_max_stream_data_can_recv, XQC_PTYPE_SHORT_HEADER);
}

```

## Summary

- **Connection-level flow control** caps total bytes in flight across all streams using `xqc_conn_flow_ctl_t` and `MAX_DATA` frames, providing a global safety net
- **Stream-level flow control** limits per-stream bandwidth using `xqc_stream_flow_ctl_t` and `MAX_STREAM_DATA` frames, ensuring fairness among concurrent streams
- Both limits are negotiated via transport parameters (`max_data`, `max_stream_data_*`) during the QUIC handshake
- XQUIC enforces these checks in [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c) before packet transmission, queuing `DATA_BLOCKED` or `STREAM_DATA_BLOCKED` frames when limits are exceeded
- Distinct error codes (`XQC_ECONN_BLOCKED` vs `XQC_ESTREAM_BLOCKED`) enable precise debugging of flow control violations

## Frequently Asked Questions

### What happens when the connection flow control limit is reached in XQUIC?

When the connection-level limit is reached, the code in [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c) (lines 351-359) marks the connection as blocked and generates an `XQC_ECONN_BLOCKED` error. The endpoint sends a `DATA_BLOCKED` frame to the peer, signaling that additional `MAX_DATA` frames are required to resume transmission.

### How does XQUIC initialize flow control limits for new connections?

XQUIC initializes connection limits in `xqc_conn_init_flow_ctl` within [`src/transport/xqc_conn.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_conn.c) using the `max_data` transport parameter. Per-stream limits are configured during `xqc_stream_open` in [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c) (lines 246-274) based on stream type (bidirectional vs unidirectional) and endpoint role (client vs server).

### Can a single stream consume the entire connection flow control window?

Without stream-level flow control, a single stream could monopolize the entire connection window. XQUIC prevents this by enforcing separate `fc_max_stream_data_can_send` constraints for each stream, ensuring that no single transfer can starve others of bandwidth.

### Which QUIC frames update flow control windows in XQUIC?

`MAX_DATA` frames update the connection-level receive window (`fc_max_data_can_recv`), processed in [`src/transport/xqc_frame.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_frame.c) (lines 600-610). `MAX_STREAM_DATA` frames update individual stream windows (`fc_max_stream_data_can_recv`), handled in the same file (lines 608-610). Both frame types are essential for sliding window updates and preventing protocol deadlock.