# Thread Safety Considerations When Using XQUIC: A Complete Developer's Guide

> Understand XQUIC thread safety. Learn essential considerations for developers to manage cross-thread data sharing and ensure robust application performance with this single-threaded library.

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

---

**XQUIC is strictly single-threaded; all engine, connection, and stream operations must occur on the thread that created the engine, requiring external synchronization for any cross-thread data sharing.**

XQUIC, Alibaba's high-performance QUIC and HTTP/3 implementation, follows a single-threaded event-driven architecture that demands careful attention to thread safety considerations when using XQUIC in multi-threaded applications. Unlike libraries that provide internal locking, XQUIC requires the application to serialize all API calls per engine instance. This guide examines the threading constraints documented in [`include/xquic/xquic.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xquic.h) and [`docs/API.md`](https://github.com/alibaba/xquic/blob/main/docs/API.md), providing practical patterns for safe integration.

## Core Threading Model (Single-Threaded Design)

XQUIC is designed as a **single-threaded library** with no internal locking mechanisms. All core objects—including the **engine** (`xqc_engine_t`), connections, streams, and the majority of public APIs—must be accessed **only from the thread that owns the engine**.

According to [`docs/API.md`](https://github.com/alibaba/xquic/blob/main/docs/API.md), "Application can create one or more engines in a process, but **multiple engines MUST NOT share one thread**, as XQUIC is **single-threaded**." This constraint is enforced by design rather than runtime checks; concurrent access from multiple threads results in race conditions and undefined behavior.

## Critical Thread Safety Constraints

### One Engine Per Thread Rule

Each engine instance must be bound to a **dedicated thread** for its entire lifecycle. The `xqc_engine_create` function establishes this binding, and all subsequent calls—including `xqc_engine_packet_process`, `xqc_engine_main_logic`, and `xqc_engine_finish_recv`—must occur on that same thread.

Running multiple engines on a single thread is explicitly prohibited. If your application requires multiple engines, assign each to a separate thread with proper isolation.

### Callback Execution Context

All engine-layer callbacks—including timer handlers, socket write functions, and logging callbacks—are invoked **synchronously** from the engine's thread. As implemented in [`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c), these callbacks execute without any internal serialization.

Do **not** spawn new threads inside a callback unless you first **copy required data** and protect it with your own synchronization primitives. Blocking operations within callbacks stall the entire engine, delaying packet processing and timer handling.

### Non-Thread-Safe APIs

Specific functions are explicitly marked as non-thread-safe in the public headers. For example, [`include/xquic/xquic.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xquic.h) states: "*This function is not thread‑safe.*" regarding `xqc_log_disable`.

Call such configuration functions **before** the engine starts processing packets, or guard them with an external lock if runtime modification is necessary. The `xqc_set_event_timer_pt` function and other setup routines follow the same pattern.

## Practical Implementation Patterns

### Single-Threaded Event Loop

The canonical usage pattern involves creating the engine on a dedicated thread and running an event loop that processes packets and timers sequentially:

```c
/* 1️⃣  Create the engine – done once, on the dedicated thread */
xqc_engine_t *engine = xqc_engine_create(
        XQC_ENGINE_CLIENT,          /* role */
        &engine_cfg,                /* config */
        &ssl_cfg,                   /* TLS config */
        &engine_cbs,                /* callbacks (timer, log, etc.) */
        NULL);                      /* user_data */

/* 2️⃣  Register ALPN (e.g. HTTP/3) */
xqc_engine_register_alpn(engine, "h3", 2, &h3_cbs);

/* 3️⃣  Main event loop – single thread! */
while (running) {
    /* a) read UDP packets from the socket */
    ssize_t n = recvfrom(sock, buf, sizeof(buf), 0,
                         (struct sockaddr *)&peer, &peerlen);
    if (n > 0) {
        /* b) hand the packet to XQUIC */
        xqc_engine_packet_process(engine, buf, n,
                                  (struct sockaddr *)&local, local_len,
                                  (struct sockaddr *)&peer,  peer_len,
                                  now_usec(), NULL);
    }

    /* c) let XQUIC run its timers and state machines */
    xqc_engine_main_logic(engine);

    /* d) optional: finish batch processing */
    xqc_engine_finish_recv(engine);
}

```

If you need **background work** (e.g., file I/O or database access), run those tasks on **separate threads** and communicate results back to the XQUIC thread via a lock-free queue or a simple `pipe()` that the XQUIC thread polls.

### Protecting Shared Resources

When application-level objects must be accessed from both the XQUIC thread and other worker threads, use external synchronization primitives:

```c
typedef struct {
    uint64_t total_bytes_sent;
    uint64_t total_bytes_recv;
    pthread_mutex_t lock;          /* <-- external mutex */
} app_stats_t;

app_stats_t g_stats = {0, 0, PTHREAD_MUTEX_INITIALIZER};

/* XQUIC stream-write callback (runs on XQUIC thread) */
static ssize_t my_write_cb(const unsigned char *buf, size_t size,
                           const struct sockaddr *peer, socklen_t peerlen,
                           void *conn_user_data)
{
    /* Send data to the network … */

    /* Update shared stats – protect with mutex because other threads read it */
    pthread_mutex_lock(&g_stats.lock);
    g_stats.total_bytes_sent += size;
    pthread_mutex_unlock(&g_stats.lock);
    return (ssize_t)size;
}

/* Another worker thread that periodically logs the stats */
void *stat_printer(void *arg)
{
    while (1) {
        sleep(5);
        pthread_mutex_lock(&g_stats.lock);
        printf("Sent=%lu, Recv=%lu\n",
               g_stats.total_bytes_sent, g_stats.total_bytes_recv);
        pthread_mutex_unlock(&g_stats.lock);
    }
    return NULL;
}

```

## Key Source Files and Functions

Understanding the threading model requires familiarity with these specific files in the `alibaba/xquic` repository:

| File | Relevance to Thread Safety |
|------|---------------------------|
| [`include/xquic/xquic.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xquic.h) | Contains explicit "*not thread‑safe*" annotations and public API declarations for `xqc_engine_create`, `xqc_log_disable`, and `xqc_set_event_timer_pt`. |
| [`docs/API.md`](https://github.com/alibaba/xquic/blob/main/docs/API.md) | Documents the **single‑threaded** constraint and the rule that **multiple engines must not share one thread**. |
| [`src/transport/xqc_engine.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_engine.c) | Implements `xqc_engine_main_logic`, `xqc_engine_packet_process`, and `xqc_engine_finish_recv`; all internal data structures are accessed without locks. |
| [`src/transport/xqc_timer.c`](https://github.com/alibaba/xquic/blob/main/src/transport/xqc_timer.c) | Defines timer callback expectations; the user-supplied timer must invoke `xqc_engine_main_logic` on the engine's thread. |

## Summary

- **XQUIC is strictly single-threaded**: All API calls, callbacks, and timer handlers for a given engine must execute on the thread that created the engine.
- **One engine per thread**: Multiple engines in the same process require dedicated threads; never run multiple engines on a single thread.
- **No internal locking**: The library accesses internal data structures without mutexes; concurrent access from multiple threads causes undefined behavior.
- **Callbacks are synchronous**: Engine callbacks execute on the engine thread; avoid blocking operations or spawn worker threads for heavy tasks, communicating results back via queues.
- **External synchronization required**: Protect any shared application data accessed from both XQUIC callbacks and other threads with mutexes, atomics, or message passing.

## Frequently Asked Questions

### Is XQUIC thread-safe?

No, XQUIC is explicitly **not thread-safe** by design. According to the source code in [`include/xquic/xquic.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xquic.h) and documentation in [`docs/API.md`](https://github.com/alibaba/xquic/blob/main/docs/API.md), the library operates as a single-threaded engine. All functions—including `xqc_engine_packet_process`, `xqc_engine_main_logic`, and connection callbacks—must be called from the single thread that owns the engine instance.

### Can I share an XQUIC engine between multiple threads?

No, you cannot share an XQUIC engine between threads. The engine must remain on the thread where it was created via `xqc_engine_create` for its entire lifecycle. While you can create multiple engines in a single process, [`docs/API.md`](https://github.com/alibaba/xquic/blob/main/docs/API.md) explicitly states that **multiple engines must not share one thread**. Each engine requires its own dedicated thread with proper isolation.

### How do I handle blocking I/O operations in XQUIC callbacks?

You must **never perform blocking operations** inside XQUIC callbacks such as socket write handlers or timer callbacks, as these execute synchronously on the engine thread and will stall packet processing. Instead, offload blocking work (file I/O, database queries) to separate worker threads. Communicate results back to the XQUIC thread via lock-free queues, pipes, or other thread-safe mechanisms that the engine can poll in its main event loop.

### Are XQUIC configuration functions thread-safe?

Most XQUIC configuration functions are **not thread-safe**, particularly those that modify global or engine-wide settings. For example, `xqc_log_disable` is explicitly marked in [`include/xquic/xquic.h`](https://github.com/alibaba/xquic/blob/main/include/xquic/xquic.h) as "not thread-safe." You should configure the engine—setting parameters, registering ALPNs, and adjusting log settings—**before** starting packet processing, or protect these calls with external mutexes if runtime modification is absolutely necessary.