# Understanding the Lifecycle of a QUIC Connection in XQUIC

> Explore the QUIC connection lifecycle in XQUIC, detailing seven distinct states from initialization to closure. Understand the deterministic state machine for efficient network communication.

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

---

**The lifecycle of a QUIC connection in XQUIC follows a deterministic state machine defined by `xqc_conn_state_t`, progressing through seven distinct phases from initialization to closure.**

XQUIC, Alibaba's open-source QUIC protocol implementation, manages connection lifecycles through a robust state machine tracked in the `xqc_connection_t` structure. Understanding how a connection moves from `XQC_CONN_STATE_CLIENT_INIT` to `XQC_CONN_STATE_CLOSED` is essential for debugging handshake failures, optimizing performance, and implementing graceful shutdowns in production applications.

## XQUIC Connection State Machine Overview

The connection state is stored in `conn->conn_state` within the `xqc_connection_t` structure, defined in [`src/transport/xqc_conn.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_conn.c) at line 916. The enum `xqc_conn_state_t` defines all possible states, while state transitions are primarily driven by crypto stream operations in [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c) (lines 1062-1138).

State transitions occur asynchronously as the TLS handshake progresses through Initial, Handshake, and 1-RTT packet number spaces. Each state change reflects the completion of specific cryptographic milestones or application-level close requests.

## The Seven Phases of a QUIC Connection Lifecycle in XQUIC

### 1. Initialization

The lifecycle begins when `xqc_conn_create()` allocates a connection object from the memory pool and assigns a role-specific initial state.

- **Client connections** enter `XQC_CONN_STATE_CLIENT_INIT`
- **Server connections** enter `XQC_CONN_STATE_SERVER_INIT`

This phase establishes the connection ID (DCID/SCID), initializes the crypto context, and prepares the connection for packet processing. The state is set immediately after pool allocation in [`src/transport/xqc_conn.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_conn.c) at line 916.

### 2. Initial Packet Exchange

During this phase, endpoints exchange Initial packets containing the TLS ClientHello and ServerHello messages. The state machine tracks whether each side has sent and received Initial packets.

**Client transitions:**
- `XQC_CONN_STATE_CLIENT_INITIAL_SENT` after transmitting the first Initial packet
- `XQC_CONN_STATE_CLIENT_INITIAL_RECVD` upon receiving the server's Initial packet

**Server transitions:**
- `XQC_CONN_STATE_SERVER_INITIAL_RECVD` when the client's Initial arrives
- `XQC_CONN_STATE_SERVER_INITIAL_SENT` after responding with its own Initial

These transitions occur in `xqc_crypto_stream_on_write()` (line 1126) and `xqc_crypto_stream_on_read()` (line 1062) within [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c), where the crypto layer notifies the transport of completed send or receive operations.

### 3. Handshake Packets

Once Initial keys are established, endpoints transition to exchanging Handshake packets encrypted with handshake keys. This phase completes the TLS handshake authentication.

**Key states:**
- `XQC_CONN_STATE_CLIENT_HANDSHAKE_RECVD` (client side)
- `XQC_CONN_STATE_SERVER_HANDSHAKE_RECVD` (server side)

The transition happens in `xqc_crypto_stream_on_read()` (lines 1086-1098) when data arrives on the handshake crypto stream and the TLS layer successfully processes the encrypted handshake messages.

### 4. Establishment

The connection reaches `XQC_CONN_STATE_ESTABED` when both sides have completed the TLS handshake and can send application data protected with 1-RTT keys.

This transition occurs when:
- The server sends its final Handshake packet (`xqc_crypto_stream_on_write`, line 1100)
- The client receives the Handshake packet and marks the handshake complete via `xqc_conn_check_handshake_complete()`

Once established, the connection state remains stable while application streams and datagrams flow between endpoints.

### 5. Closing

When the application calls `xqc_conn_close()` or a protocol error occurs, the connection enters `XQC_CONN_STATE_CLOSING`. This initiates the graceful shutdown sequence.

The state change is performed by the engine in [`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c) at line 3241. In this state, the connection stops accepting new streams and begins sending CONNECTION_CLOSE frames to the peer.

### 6. Draining

After entering the closing state and acknowledging all in-flight packets, the connection transitions to `XQC_CONN_STATE_DRAINING`. During draining, the connection remains alive to allow peer acknowledgments to arrive, but sends no new packets.

This transition occurs in [`src/transport/xqc_frame.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_frame.c) around line 1125 when all streams are shut down and the closing handshake completes.

### 7. Closed

The final state `XQC_CONN_STATE_CLOSED` indicates that all timers are cancelled, resources are reclaimed, and the connection is removed from the engine's hash table.

The engine forces this state when the draining timer expires or an unrecoverable error occurs, as seen in [`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c) at line 713.

## Key Source Files and Functions

Understanding the lifecycle requires familiarity with these core files:

- **[`src/transport/xqc_conn.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_conn.c)** – Defines `xqc_conn_state_t`, default state strings, and `xqc_conn_create()` which initializes the state (lines 441-452, 916).

- **[`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c)** – Contains `xqc_crypto_stream_on_read()` and `xqc_crypto_stream_on_write()` which drive state transitions during the handshake (lines 1062-1138).

- **[`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c)** – Handles connection closure, draining, and final cleanup via `xqc_conn_close()` (line 3241) and the closed state transition (line 713).

- **[`src/transport/xqc_frame.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_frame.c)** – Implements guard clauses that drop frames when the connection is `CLOSING` or `DRAINING` (line 1125).

- **[`src/transport/xqc_packet.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_packet.c)** – Manages packet number space selection based on current connection state.

## Practical Code Examples

### Creating a Client Connection and Monitoring States

The following example demonstrates creating a client connection and polling through the lifecycle states until establishment or closure:

```c
/* demo_client.c – simplified state monitoring */
xqc_engine_t *engine = xqc_engine_create(&engine_cfg, &log_cb);
xqc_conn_settings_t settings = internal_default_conn_settings;
xqc_cid_t dcid, scid;

/* generate connection IDs */
xqc_generate_cid(engine, NULL, &dcid, 0);
xqc_generate_cid(engine, NULL, &scid, 0);

/* create client connection */
xqc_connection_t *conn = xqc_conn_create(engine, &dcid, &scid,
                                         &settings, NULL, XQC_CONN_TYPE_CLIENT);

printf("initial state = %s\n", xqc_conn_state_2_str(conn->conn_state));

/* drive handshake and monitor states */
while (conn->conn_state != XQC_CONN_STATE_ESTABED &&
       conn->conn_state != XQC_CONN_STATE_CLOSED) {
    xqc_engine_process_events(engine);
    printf("current state = %s\n", xqc_conn_state_2_str(conn->conn_state));
}

```

**Key implementation details:**

- `xqc_conn_create()` initializes the state to `XQC_CONN_STATE_CLIENT_INIT` (source line 916 in [`src/transport/xqc_conn.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_conn.c))
- `xqc_engine_process_events()` drives the state machine through crypto stream callbacks
- State strings are retrieved via `xqc_conn_state_2_str()` for debugging

### Gracefully Closing a Connection

This example shows how to initiate a graceful shutdown and monitor the connection through the closing and draining phases:

```c
/* Initiate graceful shutdown */
xqc_int_t ret = xqc_conn_close(conn, XQC_ERR_SUCCESS, "normal shutdown");
if (ret != XQC_OK) {
    fprintf(stderr, "close failed: %d\n", ret);
}

/* Monitor CLOSING → DRAINING → CLOSED transition */
while (conn->conn_state != XQC_CONN_STATE_CLOSED) {
    xqc_engine_process_events(engine);
    
    const char *state_str = xqc_conn_state_2_str(conn->conn_state);
    printf("shutdown state: %s\n", state_str);
    
    if (conn->conn_state == XQC_CONN_STATE_DRAINING) {
        /* No new packets will be sent; only waiting for peer ACKs */
    }
}

```

**State transition details:**

- `xqc_conn_close()` sets the state to `XQC_CONN_STATE_CLOSING` ([`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c) line 3241)
- The engine transitions to `XQC_CONN_STATE_DRAINING` once all streams are shut down ([`src/transport/xqc_frame.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_frame.c) line 1125)
- Final `XQC_CONN_STATE_CLOSED` occurs when the draining timer expires ([`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c) line 713)

## Summary

- **XQUIC implements a deterministic state machine** defined by `xqc_conn_state_t` that tracks every phase of a QUIC connection from initialization to final closure.
- **Seven distinct phases** govern the lifecycle: Initialization, Initial Packet Exchange, Handshake Packets, Establishment, Closing, Draining, and Closed.
- **State transitions are crypto-driven**, occurring primarily in `xqc_crypto_stream_on_read()` and `xqc_crypto_stream_on_write()` within [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c) as the TLS handshake progresses through Initial, Handshake, and 1-RTT encryption levels.
- **Graceful shutdown requires state awareness**, as the connection moves through `CLOSING` (sending CONNECTION_CLOSE frames), `DRAINING` (waiting for peer acknowledgments), and finally `CLOSED` (resource reclamation) as implemented in [`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c).

## Frequently Asked Questions

### What is xqc_conn_state_t in XQUIC?

`xqc_conn_state_t` is the core enumeration that defines all possible states of a QUIC connection in the XQUIC library. Defined in [`src/transport/xqc_conn.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_conn.c), it includes values such as `XQC_CONN_STATE_CLIENT_INIT`, `XQC_CONN_STATE_ESTABED`, and `XQC_CONN_STATE_CLOSED`. The state is stored in the `conn_state` field of the `xqc_connection_t` structure and drives all connection lifecycle decisions throughout the codebase.

### How does XQUIC handle the TLS handshake state transitions?

XQUIC handles TLS handshake transitions through crypto stream callbacks in [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c). When the TLS layer sends data, `xqc_crypto_stream_on_write()` updates the state to "sent" variants (e.g., `XQC_CONN_STATE_CLIENT_INITIAL_SENT`). When data is received, `xqc_crypto_stream_on_read()` (line 1062) advances the state to "received" variants (e.g., `XQC_CONN_STATE_CLIENT_INITIAL_RECVD`). The handshake completes when `xqc_conn_check_handshake_complete()` transitions the state to `XQC_CONN_STATE_ESTABED`.

### What is the difference between CLOSING and DRAINING states in XQUIC?

The `CLOSING` state indicates that the application or protocol has initiated shutdown, and XQUIC begins sending CONNECTION_CLOSE frames to the peer while refusing new streams. The `DRAINING` state follows once all in-flight packets are acknowledged and all streams are shut down; during draining, the connection remains alive only to process peer acknowledgments but transmits no new data. The transition from `CLOSING` to `DRAINING` occurs in [`src/transport/xqc_frame.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_frame.c) around line 1125, while the final transition to `CLOSED` happens in [`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c) at line 713 when the draining timer expires.

### Where is the connection state updated during packet processing?

Connection state updates during packet processing primarily occur in [`src/transport/xqc_stream.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_stream.c) within the crypto stream handlers. The `xqc_crypto_stream_on_read()` function (line 1062) updates states when Initial and Handshake packets are successfully parsed, while `xqc_crypto_stream_on_write()` (line 1126) updates states when these packets are transmitted. Additionally, state guards in [`src/transport/xqc_frame.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_frame.c) (line 1125) and [`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c) (lines 713 and 3241) manage transitions during closure and error conditions.