# flare.runtime.io_uring

> A module of flare_mlake's API.

``io_uring`` backend substrate.

Lands the direct-syscall FFI + ``IoUringRing`` setup/teardown
primitive for the Linux 5.1+ ``io_uring`` reactor backend.
This is the **substrate**: the syscall wrappers, the
``io_uring_params`` struct, runtime feature detection, and
allocation/teardown of a kernel ring. Submission-queue / completion-
queue plumbing (SQE construction, CQE drain, multishot accept /
recv / send) lands in follow-up commits that bolt onto this
primitive without changing its public API.

Why io_uring
------------

epoll/kqueue need 2-3 syscalls per request on the keep-alive
hot path: ``epoll_wait`` → ``recvmsg`` → ``sendmsg``. At
realistic high-throughput targets (>200K req/s on 4 workers),
that's ≥ 660 K syscalls / s per worker. Each syscall costs a
syscall instruction (≈ 100-200 cycles), a context switch into
the kernel, and a TLB flush on speculation-mitigation kernels.

``io_uring`` (RFC-equivalent: Jens Axboe's 2019 LWN articles +
``man 7 io_uring``) lets the application **submit + reap many
operations per syscall** via a kernel-shared SQ/CQ ring.
Multishot accept/recv/send mean accept-loop and per-connection
recv don't need to be re-armed after each completion. For
plaintext TFB, the syscall count drops from 3-per-request to
~0.1-per-request.

Production servers built on io_uring (tigerbeetle, scylla-seastar,
glommio) routinely measure 2-5x throughput improvements vs.
epoll on the same kernel.

Why ship the substrate first
----------------------------

io_uring's full support surface is vast — the kernel ABI grew
from 7 ops in 5.1 (Read / Write / Fsync / ReadFixed /
WriteFixed / PollAdd / PollRemove) to 40+ ops by 6.10 (Provide-
Buffers, BufferRing, Multishot variants, Linked, Hardlinked,
Splice, Tee, ZeroCopy, Discard, Bind, Listen, Sendmsg-zerocopy
…). flare needs only a small subset for HTTP/1.1 (multishot
accept on the listener fd + recv on each accepted fd + send /
sendv for the response). Shipping the substrate first lets the
follow-up commits land that subset incrementally without
re-touching the syscall-ABI plumbing.

What this module provides
-------------------------

* **Syscall numbers** — ``SYS_IO_URING_SETUP``,
  ``SYS_IO_URING_ENTER``, ``SYS_IO_URING_REGISTER`` for x86_64
  / aarch64 Linux. (io_uring is a Linux-only feature; macOS /
  BSD callers fall through to the existing kqueue path.)
* **``IoUringParams``** — Mojo mirror of the
  ``struct io_uring_params`` ABI. Holds the ring sizes
  (``sq_entries`` / ``cq_entries``), the kernel-returned
  feature flags, and the per-region offset blobs needed for
  the upcoming SQ/CQ mmaps.
* **``io_uring_setup(entries, params) -> Int``** — wraps the
  ``SYS_io_uring_setup(2)`` syscall. Returns the ring file
  descriptor or a negative ``-errno`` on failure.
* **``io_uring_enter(fd, to_submit, min_complete, flags) -> Int``**
  — wraps ``SYS_io_uring_enter(2)``. The submission /
  completion driver call.
* **``io_uring_register(fd, opcode, arg, nr_args) -> Int``** —
  wraps ``SYS_io_uring_register(2)``. Used to register fixed
  buffers / files / probe the kernel feature set.
* **``IoUringRing(entries)``** — owning wrapper that:
  - Calls ``io_uring_setup`` to allocate a kernel ring of
    ``entries`` SQEs (rounded up to a power of two by the
    kernel).
  - Closes the ring fd on drop.
  - Exposes ``fd()``, ``params()``, and the (currently
    unmmapped) per-region offsets.
* **``is_io_uring_available()``** — runtime feature detection.
  Tries ``io_uring_setup(1, ...)``; on success closes the
  ring + returns ``True``; on EINVAL / ENOSYS / EPERM
  (kernel too old, sandbox, …) returns ``False`` without
  raising.

Why direct syscall FFI vs liburing
-----------------------------------

``liburing`` is the upstream userspace library that wraps these
syscalls + provides the standard SQE/CQE constructors. flare
chooses the direct-syscall route because:

1. **Zero new conda dependency** — io_uring's ABI is stable
   since Linux 5.1 (May 2019); the syscall numbers don't drift.
   Linking against liburing.so adds a deployment dependency
   that conda-forge does ship (``liburing``) but the
   Mojo-FFI-via-shared-library plumbing is awkward (no
   ``ctypes``-equivalent ergonomics yet).
2. **Smaller binary surface** — liburing is ~40 KB; the
   subset flare needs (setup / enter / register + SQE/CQE
   layout) is ~500 lines of FFI + struct definitions in
   pure Mojo.
3. **Better integration with flare's typed errors** — the
   syscall wrapper raises ``IoError`` / ``BrokenPipe`` /
   ``Timeout`` directly instead of returning C ``errno``.

Wiring into the ``Reactor`` (the comptime branch that selects
``UringReactor`` vs ``EpollReactor`` vs ``KqueueReactor`` based
on kernel feature detection) is a follow-up commit that bolts
onto this primitive without changing the existing
``flare.runtime.Reactor`` public surface.

## Structs

### `IoUringParams`

```mojo
struct IoUringParams
```

Mojo mirror of the kernel ``struct io_uring_params``.

Field layout exactly matches the kernel ABI (see
``include/uapi/linux/io_uring.h`` in the Linux source). The
struct is 120 bytes total; the high 12 bytes are reserved.

Fields:
    sq_entries: Kernel-returned actual SQ size (rounded up
                to a power of two from the caller's request).
    cq_entries: Kernel-returned actual CQ size (typically
                ``2 * sq_entries`` unless ``IORING_SETUP_CQSIZE``
                is set in flags).
    flags: ``IORING_SETUP_*`` flags. 0 for the default
           configuration; set ``IORING_SETUP_SQPOLL`` for a
           kernel-side polling thread, etc.
    sq_thread_cpu / sq_thread_idle: ``IORING_SETUP_SQ_AFF`` /
           ``IORING_SETUP_SQPOLL`` knobs.
    features: Kernel-returned ``IORING_FEAT_*`` flags
           describing what this kernel supports.
    wq_fd: Worker-queue fd for cross-ring sharing (unused
           in the default setup).
    sq_off / cq_off: Per-region offsets returned by the
           kernel; the caller mmaps the ring fd at these
           offsets to get the SQ / CQ control regions.

Implements: `Movable`

Fields:

- `sq_entries: UInt32`
- `cq_entries: UInt32`
- `flags: UInt32`
- `sq_thread_cpu: UInt32`
- `sq_thread_idle: UInt32`
- `features: UInt32`
- `wq_fd: UInt32`
- `resv0: UInt32`
- `resv1: UInt32`
- `resv2: UInt32`
- `sq_off_head: UInt32`
- `sq_off_tail: UInt32`
- `sq_off_ring_mask: UInt32`
- `sq_off_ring_entries: UInt32`
- `sq_off_flags: UInt32`
- `sq_off_dropped: UInt32`
- `sq_off_array: UInt32`
- `sq_off_resv1: UInt32`
- `sq_off_user_addr_lo: UInt32`
- `sq_off_user_addr_hi: UInt32`
- `cq_off_head: UInt32`
- `cq_off_tail: UInt32`
- `cq_off_ring_mask: UInt32`
- `cq_off_ring_entries: UInt32`
- `cq_off_overflow: UInt32`
- `cq_off_cqes: UInt32`
- `cq_off_flags: UInt32`
- `cq_off_resv1: UInt32`
- `cq_off_user_addr_lo: UInt32`
- `cq_off_user_addr_hi: UInt32`

#### `empty`

```mojo
def empty() -> Self
```

Construct an all-zeros ``IoUringParams`` for use as the ``params`` arg of :func:`io_uring_setup`.

Caller may then set ``flags`` / ``sq_thread_cpu`` /
``sq_thread_idle`` before passing to
``io_uring_setup``.

### `IoUringRing`

```mojo
struct IoUringRing
```

Owning wrapper over an io_uring kernel ring.

Allocates the ring on construction and closes the fd on
drop. Future commits will mmap the SQ / CQ regions onto
public ``sq()`` / ``cq()`` accessors so callers can submit
SQEs and reap CQEs.

The kernel-filled ``io_uring_params`` struct (120 bytes) is
held as a raw byte buffer; field accessors read 32-bit LE
values out of it via fixed offsets.

Fields:
    _fd: Ring file descriptor returned by
         ``io_uring_setup``. -1 means "not set up yet"
         (after move-out or on a failed setup).
    _params_buf: Owning pointer to the 120-byte params
                 buffer. Freed on drop.

Implements: `Movable`

#### `__init__`

```mojo
def __init__(out self, entries: Int, setup_flags: UInt32 = UInt32(0), sq_thread_cpu: UInt32 = UInt32(0), sq_thread_idle: UInt32 = UInt32(0))
```

Set up an io_uring with ``entries`` SQEs.

Args:

- `entries`: Number of SQEs (kernel rounds up to a power
         of two; max 32 768).
- `setup_flags`: Bitwise-OR of ``IORING_SETUP_*`` flags
    from :mod:`flare.runtime.io_uring_sqe`. Default 0
    (interrupt-driven, single-thread, no kernel
    scheduler hints) matches the historical behaviour.
    Bufring callers typically pass
    ``COOP_TASKRUN | TASKRUN_FLAG | SUBMIT_ALL`` and
    add ``SINGLE_ISSUER | DEFER_TASKRUN`` on kernels
    >= 6.1 (probe via :func:`is_io_uring_available`'s
    features field). The kernel rejects the
    io_uring_setup with EINVAL if it doesn't support a
    requested flag, so callers can pass the highest
    set they want and fall back on EINVAL.
- `sq_thread_cpu`: For ``IORING_SETUP_SQPOLL +
    IORING_SETUP_SQ_AFF``, the CPU to pin the kernel
    SQPOLL thread to. Ignored otherwise.
- `sq_thread_idle`: For ``IORING_SETUP_SQPOLL``, the
    idle period in milliseconds before the kernel
    SQPOLL thread sleeps. Ignored otherwise.

Raises: Error: On ``io_uring_setup`` failure.

#### `__deinit__`

```mojo
def __deinit__(deinit self)
```

Close the ring fd + free the params buffer.

#### `fd`

```mojo
def fd(self) -> Int
```

Return the ring file descriptor.

#### `sq_entries`

```mojo
def sq_entries(self) -> Int
```

Return the kernel-allocated SQ size (≥ caller's request, rounded up to a power of two).

#### `cq_entries`

```mojo
def cq_entries(self) -> Int
```

Return the kernel-allocated CQ size (default ``2 * sq_entries``).

#### `features`

```mojo
def features(self) -> Int
```

Return the kernel-reported ``IORING_FEAT_*`` flags.

## Functions

### `io_uring_setup`

```mojo
def io_uring_setup(entries: Int, params: Pointer[UInt8]) -> Int
```

Wrap ``SYS_io_uring_setup(2)`` via the libc ``syscall(2)`` multiplexer.

Args:

- `entries`: Number of SQEs to allocate (kernel rounds up
         to a power of two; max 32 768 in current
         kernels).
- `params`: Pointer to a 120-byte ``IoUringParams`` buffer
        (in/out — kernel fills in the per-region
        offsets and feature flags).

### `io_uring_enter`

```mojo
def io_uring_enter(fd: Int, to_submit: Int, min_complete: Int, flags: Int) -> Int
```

Wrap ``SYS_io_uring_enter(2)`` via ``syscall(2)``.

Args:

- `fd`: Ring file descriptor returned by
    :func:`io_uring_setup`.
- `to_submit`: Number of SQEs to submit from the SQ ring.
- `min_complete`: Minimum number of completions to wait for
              before returning. 0 = non-blocking submit.
- `flags`: ``IORING_ENTER_*`` flags.

### `io_uring_enter_timeout`

```mojo
def io_uring_enter_timeout(fd: Int, to_submit: Int, min_complete: Int, flags: Int, timeout_ms: Int) -> Int
```

``SYS_io_uring_enter(2)`` that waits at most ``timeout_ms``.

Passes ``IORING_ENTER_EXT_ARG`` with a ``struct
io_uring_getevents_arg`` (``sigmask``, ``sigmask_sz``, ``pad``, ``ts``;
24 bytes) whose ``ts`` points at a ``struct __kernel_timespec``. The
kernel copies both before it waits, so stack storage is enough.

Args:

- `fd`: Ring file descriptor.
- `to_submit`: Number of SQEs to submit from the SQ ring.
- `min_complete`: Completions to wait for before returning.
- `flags`: ``IORING_ENTER_*`` flags (``EXT_ARG`` is added here).
- `timeout_ms`: Longest wait, in milliseconds (clamped to >= 0).

### `io_uring_register`

```mojo
def io_uring_register(fd: Int, opcode: Int, arg: Int, nr_args: Int) -> Int
```

Wrap ``SYS_io_uring_register(2)`` via ``syscall(2)``.

Used to register fixed buffers / files / probe the kernel
feature set.

Args:

- `fd`: Ring file descriptor.
- `opcode`: ``IORING_REGISTER_*`` opcode.
- `arg`: Pointer-or-int argument (cast to ``UInt64`` for
     the syscall ABI).
- `nr_args`: Number of arguments at ``arg``.

### `is_io_uring_available`

```mojo
def is_io_uring_available() -> Bool
```

Runtime check: does the host kernel expose io_uring?

Tries to set up a 1-entry ring; on success closes it +
returns ``True``. On EINVAL / ENOSYS / EPERM (kernel too
old, sandbox, container without ``io_uring_setup`` syscall
permitted, …) returns ``False`` without raising.

Useful for the upcoming ``Reactor`` comptime branch:
``UringReactor`` is selected when both
``CompilationTarget.is_linux()`` and
``is_io_uring_available()`` are true.

## Aliases

### `SYS_IO_URING_SETUP`

```mojo
comptime SYS_IO_URING_SETUP = Int(425)
```

``SYS_io_uring_setup`` syscall number on x86_64 + aarch64 Linux. Stable since Linux 5.1.

### `SYS_IO_URING_ENTER`

```mojo
comptime SYS_IO_URING_ENTER = Int(426)
```

``SYS_io_uring_enter`` syscall number. Stable since 5.1.

### `SYS_IO_URING_REGISTER`

```mojo
comptime SYS_IO_URING_REGISTER = Int(427)
```

``SYS_io_uring_register`` syscall number. Stable since 5.1.
