Skip to content

Configuration

zerg is configured through three types: EngineOptions (top-level), ReactorConfig (per-reactor), and AcceptorConfig (acceptor thread). All have sensible defaults so you can start with just a port number.

EngineOptions

The top-level configuration passed to new Engine(options).

PropertyTypeDefaultDescription
ReactorCountint1Number of reactor threads to spawn. Each gets its own io_uring instance.
Ipstring"0.0.0.0"IP address to bind the listening socket to. Use "::" for IPv6.
Portushort8080TCP port to listen on.
Backlogint65535Kernel listen backlog for pending connections.
AcceptorConfigAcceptorConfignew()Configuration for the acceptor ring and event loop.
ReactorConfigsReactorConfig[]nullPer-reactor configuration array. Auto-initialized with defaults if null. Must have at least ReactorCount entries if provided.

Example

var engine = new Engine(new EngineOptions
{
    Ip = "0.0.0.0",
    Port = 8080,
    ReactorCount = 12,
    Backlog = 65535
});

ReactorConfig

Configuration for a single reactor’s io_uring instance and event loop. This is a sealed record – all properties have defaults.

PropertyTypeDefaultDescription
RingFlagsuintSINGLE_ISSUER | DEFER_TASKRUNio_uring setup flags. See Ring Flags below.
SqCpuThreadint-1CPU core to pin the SQPOLL kernel thread to. Only used with IORING_SETUP_SQPOLL. -1 = kernel decides.
SqThreadIdleMsuint100How long (ms) the SQPOLL kernel thread stays alive without submissions before sleeping.
RingEntriesuint8192SQ/CQ size. Upper bound on in-flight operations (recv, send, cancel) the reactor can have at once.
RecvBufferSizeint32768 (32 KB)Size of each receive buffer in the buffer ring. Larger values reduce syscalls for large payloads.
BufferRingEntriesint16384Number of pre-allocated recv buffers. Must be a power of two.
BatchCqesint4096Maximum CQEs processed per loop iteration. Larger improves throughput under load.
MaxConnectionsPerReactorint8192Upper bound on concurrent connections. Should be <= RingEntries.
CqTimeoutlong1_000_000 (1 ms)Timeout in nanoseconds passed to io_uring_wait_cqes(). Lower = lower tail latency, higher CPU.

Example: Per-Reactor Configuration

var engine = new Engine(new EngineOptions
{
    Port = 8080,
    ReactorCount = 4,
    ReactorConfigs = Enumerable.Range(0, 4).Select(_ => new ReactorConfig(
        RecvBufferSize: 64 * 1024,       // 64 KB recv buffers
        BufferRingEntries: 32 * 1024,     // 32K buffers per reactor
        CqTimeout: 500_000                // 0.5 ms timeout
    )).ToArray()
});

AcceptorConfig

Configuration for the acceptor thread’s io_uring instance. This is a sealed record.

PropertyTypeDefaultDescription
RingFlagsuint0io_uring setup flags for the acceptor ring. SQPOLL is usually unnecessary here.
SqCpuThreadint-1CPU core for SQPOLL thread.
SqThreadIdleMsuint100SQPOLL idle timeout in milliseconds.
RingEntriesuint8192SQ/CQ size. Bounds in-flight accept completions.
BatchSqesuint4096Max accepts processed per loop iteration.
CqTimeoutlong100_000_000 (100 ms)Wait timeout in nanoseconds. Higher than reactor default since accepts are burst-driven.
IPVersionIPVersionIPv6DualStackIP stack for the listening socket.

IPVersion Enum

ValueDescription
IPv4OnlyCreates AF_INET socket. Only IPv4 clients can connect.
IPv6DualStackCreates AF_INET6 socket with IPV6_V6ONLY=0. Accepts both IPv4 and IPv6 clients. IPv4 clients appear as ::ffff:a.b.c.d.

Example: Custom Acceptor

var engine = new Engine(new EngineOptions
{
    Port = 443,
    ReactorCount = 8,
    AcceptorConfig = new AcceptorConfig(
        RingEntries: 16384,
        CqTimeout: 50_000_000,           // 50 ms
        IPVersion: IPVersion.IPv4Only
    )
});

Ring Flags

io_uring setup flags control how the kernel processes submissions and completions. You can combine flags with bitwise OR.

FlagValueDescription
IORING_SETUP_SQPOLL1 << 1Kernel thread polls the SQ, eliminating submit syscalls. Trades a CPU core for lower latency.
IORING_SETUP_SQ_AFF1 << 2Pin the SQPOLL kernel thread to the CPU specified by SqCpuThread.
IORING_SETUP_SINGLE_ISSUER1 << 12Optimize for a single submitting thread. Default for reactors.
IORING_SETUP_DEFER_TASKRUN1 << 13Defer kernel task_work execution, reducing latency spikes. Default for reactors.
IORING_SETUP_CQSIZE1 << 3Allow CQ size different from SQ size.
IORING_SETUP_CLAMP1 << 4Clamp queue sizes to kernel-supported limits.

SQPOLL Example

// Enable SQPOLL on reactor 0, pinned to CPU 2
var config = new ReactorConfig(
    RingFlags: ABI.IORING_SETUP_SQPOLL | ABI.IORING_SETUP_SQ_AFF | ABI.IORING_SETUP_SINGLE_ISSUER,
    SqCpuThread: 2,
    SqThreadIdleMs: 200
);

Memory Budget

A rough estimate of per-reactor memory usage:

ComponentFormulaDefault
Buffer ringBufferRingEntries * RecvBufferSize16384 * 32 KB = 512 MB
Write slabsMaxConnectionsPerReactor * 16 KB8192 * 16 KB = 128 MB
Ring entriesRingEntries * sizeof(SQE/CQE)~1 MB

For a 4-reactor server with defaults, the buffer ring alone accounts for ~2 GB. Adjust BufferRingEntries and RecvBufferSize based on your workload and available memory.