6 Module ACCEPT_POOL
gullradriel edited this page 2026-10-29 20:54:40 +01:00

ACCEPT_POOL: parallel accept pool for high-performance connection handling

More...

Structures

Name
struct NETW_ACCEPT_POOL_STATS
Statistics for the accept pool.
struct NETW_ACCEPT_POOL
Structure of a parallel accept pool.

Types

Name
typedef void(*)(NETWORK *accepted, void *user_data) netw_accept_callback_t
callback type for accepted connections.

Functions

Name
int netw_accept_pool_wait(NETW_ACCEPT_POOL * pool, int timeout_sec)
Wait for all accept threads to finish after a stop request.
int netw_accept_pool_stop(NETW_ACCEPT_POOL * pool)
Request the accept pool to stop.
int netw_accept_pool_start(NETW_ACCEPT_POOL * pool)
Start the accept pool (launches accept threads).
int netw_accept_pool_get_stats(NETW_ACCEPT_POOL * pool, NETW_ACCEPT_POOL_STATS * stats)
Get a snapshot of pool statistics (thread-safe copy).
int netw_accept_pool_destroy(NETW_ACCEPT_POOL ** pool)
Destroy an accept pool.
NETW_ACCEPT_POOL * netw_accept_pool_create(NETWORK * server, size_t nb_threads, int accept_timeout, netw_accept_callback_t callback, void * user_data)
Create a new accept pool.

Defines

Name
netw_accept_pool_atomic_write_state(pool, val)
Lock-free atomic write of the accept pool state.
netw_accept_pool_atomic_read_state(pool)
Lock-free atomic read of the accept pool state.
NETW_ACCEPT_POOL_STOPPING
accept pool state: stop requested
NETW_ACCEPT_POOL_STOPPED
accept pool state: fully stopped
NETW_ACCEPT_POOL_RUNNING
accept pool state: running and accepting connections
NETW_ACCEPT_POOL_IDLE
accept pool state: idle, not yet started

Detailed Description

Overview

The accept pool provides nginx-style parallel connection acceptance: multiple threads call accept() on the same listening socket, each invoking a user callback for every accepted connection.

Server Architecture Modes

There are three common ways to structure a server's accept + handling logic. Each has different performance characteristics depending on the workload:

Single-inline: accept + handle in one thread

One thread does accept() and handles the client inline before accepting the next connection. Both accept and handling are fully serialized.

  • Pros: Simplest architecture, no synchronization overhead, no thread contention
  • Cons: Throughput limited to one connection at a time
  • Best for: Low-traffic servers, simple request/response protocols, debugging

Single-pool: one accept thread + worker thread pool

One thread calls accept() and dispatches each connection to a worker thread pool for handling. Accept is serialized but handling is parallelized.

  • Pros: Parallelizes the expensive part (client handling), simple accept logic
  • Cons: Accept is still serialized, under extreme connection rates the single accept thread becomes the bottleneck
  • Best for: Most servers where per-connection work (I/O, computation) dominates the accept overhead. This is the sweet spot for typical workloads.

Pooled: N accept threads + worker thread pool

Multiple threads call accept() on the same listening socket (nginx-style), each dispatching to a worker thread pool. Both accept and handling are parallelized.

  • Pros: Eliminates the accept bottleneck under extreme connection rates
  • Cons: Extra threads and kernel-level accept queue contention add overhead. On localhost or low-latency networks, this overhead can outweigh the benefit, making pooled mode slower than single-pool.
  • Best for: High connection rates over real networks (WAN clients), SSL/TLS servers where the handshake is part of the accept path, multi-socket NUMA systems, or any scenario where accept() itself is slow enough to serialize.

Performance Considerations

On localhost (loopback), accept() is near-instant (nanoseconds). A single accept thread can drain the kernel queue faster than clients can fill it. Adding more accept threads introduces kernel socket lock contention and context switches without benefit, single-pool will often outperform pooled mode in this case.

The pooled accept advantage appears when:

  • Clients connect over a real network with non-trivial latency
  • SSL/TLS handshakes run as part of the accept path
  • Connection rates exceed what one thread can accept (10k+ conn/sec sustained)
  • The system has many CPU cores and the kernel accept lock is the bottleneck

The example programs (ex_accept_pool_server, ex_accept_pool_client, test_accept_pool.sh) demonstrate all three modes with configurable parameters for benchmarking.

Types Documentation

typedef netw_accept_callback_t

typedef void(* netw_accept_callback_t) (NETWORK *accepted, void *user_data);

callback type for accepted connections.

Parameters:

  • accepted the newly accepted NETWORK connection (caller takes ownership)
  • user_data opaque pointer passed at pool creation

Functions Documentation

function netw_accept_pool_wait

int netw_accept_pool_wait(
    NETW_ACCEPT_POOL * pool,
    int timeout_sec
)

Wait for all accept threads to finish after a stop request.

Parameters:

  • pool the accept pool to wait on
  • timeout_sec maximum seconds to wait (0 = wait forever)

Return: TRUE if all threads joined, FALSE on timeout or error

function netw_accept_pool_stop

int netw_accept_pool_stop(
    NETW_ACCEPT_POOL * pool
)

Request the accept pool to stop.

Parameters:

  • pool the accept pool to stop

Return: TRUE on success, FALSE on error

Non-blocking, threads will finish current accept and exit.

function netw_accept_pool_start

int netw_accept_pool_start(
    NETW_ACCEPT_POOL * pool
)

Start the accept pool (launches accept threads).

Parameters:

  • pool the accept pool to start

Return: TRUE on success, FALSE on error

function netw_accept_pool_get_stats

int netw_accept_pool_get_stats(
    NETW_ACCEPT_POOL * pool,
    NETW_ACCEPT_POOL_STATS * stats
)

Get a snapshot of pool statistics (thread-safe copy).

Parameters:

  • pool the accept pool
  • stats output structure to fill

Return: TRUE on success, FALSE on error

Get a snapshot of pool statistics.

Get a snapshot of pool statistics (thread-safe copy).

function netw_accept_pool_destroy

int netw_accept_pool_destroy(
    NETW_ACCEPT_POOL ** pool
)

Destroy an accept pool.

Parameters:

  • pool pointer to the accept pool pointer (set to NULL)

Return: TRUE on success, FALSE on error

Pool must be stopped first.

Pool must be stopped first.

function netw_accept_pool_create

NETW_ACCEPT_POOL * netw_accept_pool_create(
    NETWORK * server,
    size_t nb_threads,
    int accept_timeout,
    netw_accept_callback_t callback,
    void * user_data
)

Create a new accept pool.

Parameters:

  • server listening NETWORK (must already be listening)
  • nb_threads number of accept threads
  • accept_timeout timeout in msec for accept (0=blocking, -1=nonblocking, >0=timeout ms)
  • callback function called for each accepted connection
  • user_data opaque pointer passed to callback

Return: new NETW_ACCEPT_POOL or NULL on error

Macros Documentation

define netw_accept_pool_atomic_write_state

#define netw_accept_pool_atomic_write_state(
    pool,
    val
)
__atomic_store_n(&(pool)->state, (val), __ATOMIC_RELEASE)

Lock-free atomic write of the accept pool state.

define netw_accept_pool_atomic_read_state

#define netw_accept_pool_atomic_read_state(
    pool
)
__atomic_load_n(&(pool)->state, __ATOMIC_ACQUIRE)

Lock-free atomic read of the accept pool state.

define NETW_ACCEPT_POOL_STOPPING

#define NETW_ACCEPT_POOL_STOPPING 2

accept pool state: stop requested

define NETW_ACCEPT_POOL_STOPPED

#define NETW_ACCEPT_POOL_STOPPED 3

accept pool state: fully stopped

define NETW_ACCEPT_POOL_RUNNING

#define NETW_ACCEPT_POOL_RUNNING 1

accept pool state: running and accepting connections

define NETW_ACCEPT_POOL_IDLE

#define NETW_ACCEPT_POOL_IDLE 0

accept pool state: idle, not yet started