Overview

ioxide is an io_uring runtime for .NET. It runs one reactor per core, each owning its own ring and serving connections with no locks and no thread hops - your handler resumes right where the I/O completed, on the same thread. This page is the gentle introduction; the Advanced pages go inside the machine.

The idea in one minute

Classic async servers cross a syscall (and often a thread) for every read and write. io_uring changes the deal: you stage many operations in a shared submission queue, enter the kernel once, and collect a whole batch of completions. ioxide builds a thread-per-core server on top of that - and adds the part that makes it pleasant to write: when an operation completes, the awaiting handler continues inline, on the reactor thread, with nothing scheduled anywhere.

The shape of a server

You set a config, spawn one reactor per core on its own thread, and give each a Handle - the per-connection loop. That loop reads, does whatever work, writes, and flushes; it can await a database, a cache, or a file, and resume inline.

var config = new ServerConfig { Port = 8080, ReactorCount = Environment.ProcessorCount };

var threads = new Thread[config.ReactorCount];
for (int i = 0; i < config.ReactorCount; i++)
{
    var reactor = new Reactor(i, config);
    reactor.Handle = async (r, conn) =>
    {
        try
        {
            while (true)
            {
                var snapshot = await conn.ReadAsync();          // io_uring recv, resumes inline
                while (conn.TryGetItem(snapshot, out var item)) // drain the received slices
                    if (item.HasBuffer) conn.ReturnBuffer(in item);

                conn.Write("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok"u8);
                await conn.FlushAsync();                        // io_uring send

                if (snapshot.IsClosed) return;
                conn.ResetRead();
            }
        }
        finally { conn.DecRef(); }
    };
    threads[i] = new Thread(reactor.Run) { IsBackground = false };
    threads[i].Start();
}
foreach (var t in threads) t.Join();

ioxide does not speak HTTP for you - you write the response bytes. That is deliberate: the runtime is the I/O engine, and what rides on it is yours. The landing page shows the same loop with real request parsing and a database call.

Plugging in backends

Anything your handler talks to - Postgres, Redis, files, an upstream service - is a client that rides the same ring, opened once per reactor and rented per request. The engine never names a client type, so adding one never touches the reactor:

Where to go next

For the real mechanics - the submit/wait loop, provided buffer rings, completion routing by generation, the connection lifecycle - start with Architecture, then The Reactor and The Connection. To run more than one entry point (say plaintext and TLS) from one server, see Multi-port.