#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_napi"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — NAPI busy-poll registration (`IORING_REGISTER_NAPI`, Linux 6.9).
*
* NAPI busy-polling lets a ring spin on the network stack's NAPI receive path
* for a bounded window instead of sleeping until an interrupt fires — trading
* CPU cycles for lower receive latency on networked completions. It is
* configured per-ring (not per-NIC) via `io_uring_register(IORING_REGISTER_NAPI)`,
* which `during` exposes as `Uring.registerNapi`.
*
* This example builds an `io_uring_napi` config (a busy-poll timeout in
* microseconds plus the `prefer_busy_poll` flag), registers it, prints the
* parameters on success, then tears it back down with `unregisterNapi`.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md
* § "6.9 — NAPI busy-poll, ftruncate (May 2024)".
*
* Run with: `dub run --single napi.d`
*
* Portability: NAPI busy-poll arrived in 6.9 and can additionally be gated by
* the running kernel's network configuration. If `io_uring` is unavailable, or
* `registerNapi` reports `-EINVAL`/`-EOPNOTSUPP`/`-ENOSYS`, the program prints a
* `SKIP:` line and exits 0 so it stays green in CI regardless of the host.
*/
module (module) napi_exampleio_uring — NAPI busy-poll registration (IORING_REGISTER_NAPI, Linux 6.9).
NAPI busy-polling lets a ring spin on the network stack's NAPI receive path
for a bounded window instead of sleeping until an interrupt fires — trading
CPU cycles for lower receive latency on networked completions. It is
configured per-ring (not per-NIC) via io_uring_register(IORING_REGISTER_NAPI),
which during exposes as Uring.registerNapi.
This example builds an io_uring_napi config (a busy-poll timeout in
microseconds plus the prefer_busy_poll flag), registers it, prints the
parameters on success, then tears it back down with unregisterNapi.
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md
§ "6.9 — NAPI busy-poll, ftruncate (May 2024)".
Run with: dub run --single napi.d
Portability
NAPI busy-poll arrived in 6.9 and can additionally be gated by
the running kernel's network configuration. If io_uring is unavailable, or
registerNapi reports -EINVAL/-EOPNOTSUPP/-ENOSYS, the program prints a
SKIP: line and exits 0 so it stays green in CI regardless of the host.
napi_example;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.errnoD header file for GNU/Linux
errno : (alias constant) napi_example.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL, (alias constant) napi_example.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP, (alias constant) napi_example.ENOSYS = int core.stdc.errno.ENOSYS = 38ENOSYS;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) napi_example.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
int int D main()main()
{
(struct) during.UringMain entry point to work with io_uring.
It hides SubmissionQueue and CompletionQueue behind standard range interface.
We put in SubmissionEntry entries and take out CompletionEntry entries.
Use predefined prepXX methods to fill required fields of SubmissionEntry before put or during putWith.
Note
prepXX functions doesn't touch previous entry state, just fills in operation properties. This is because for
less error prone interface it is cleared automatically when prepared using putWith. So when using on own SubmissionEntry
(outside submission queue), that would be added to the submission queue using put, be sure its cleared if it's
reused for multiple operations.
Uring (local variable) during.Uring ioio;
const (local variable) const(int) setupRetsetupRet = (local variable) during.Uring ioio.int during.setup(ref during.Uring uring, uint entries = 128u, during.io_uring.SetupFlags flags = SetupFlags.NONE) nothrow @nogc @safeSetup new instance of io_uring into provided Uring structure.
setup(8);
if ((local variable) const(int) setupRetsetupRet < 0)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: io_uring_setup failed (errno %d) — io_uring unavailable on this host", -(local variable) const(int) setupRetsetupRet);
return 0;
}
// NAPI busy-poll config: spin the NAPI receive path for up to 50us before
// falling back to interrupt-driven wakeups, and prefer busy polling.
(struct) during.io_uring.io_uring_napiArgument for IORING_REGISTER_NAPI / IORING_UNREGISTER_NAPI. Configures NAPI busy-poll
behaviour for the ring. busy_poll_to is the busy-poll timeout in microseconds.
The struct has a backward-compatible layout: zero-initialised callers leave opcode and
op_param at 0, selecting the original register/unregister behaviour. Linux 6.18+ users
can write IO_URING_NAPI_STATIC_* into opcode to manage the static NAPI tracking list.
Note
Available from Linux 6.9 (extended in 6.18 with opcode/op_param)
io_uring_napi (local variable) during.io_uring.io_uring_napi napinapi;
(local variable) during.io_uring.io_uring_napi napinapi.(field) uint during.io_uring.io_uring_napi.busy_poll_tobusy-poll timeout in microseconds
busy_poll_to = 50; // busy-poll timeout, microseconds
(local variable) during.io_uring.io_uring_napi napinapi.(field) ubyte during.io_uring.io_uring_napi.prefer_busy_pollboolean: prefer busy polling over interrupts
prefer_busy_poll = 1; // prefer busy poll over interrupts
const (local variable) const(int) regRetregRet = (local variable) during.Uring ioio.int during.Uring.registerNapi(ref scope during.io_uring.io_uring_napi napi) nothrow @nogc @trustedEnable NAPI busy-polling on the ring. napi`.busy_poll_to` is the busy-poll timeout in
microseconds, napi.prefer_busy_poll selects busy poll over interrupt-driven receive.
On return, napi is filled with the previous configuration.
Note
Available from Linux 6.9
registerNapi((local variable) during.io_uring.io_uring_napi napinapi);
if ((local variable) const(int) regRetregRet == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (local variable) const(int) regRetregRet == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP || (local variable) const(int) regRetregRet == -(constant) int core.stdc.errno.ENOSYS = 38ENOSYS)
{
// Kernel < 6.9, or NAPI busy-poll not available in this environment.
// This is an expected outcome on hosts without NAPI support.
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: IORING_REGISTER_NAPI unsupported here (errno %d) — needs Linux 6.9+ with NAPI busy-poll", -(local variable) const(int) regRetregRet);
return 0;
}
if ((local variable) const(int) regRetregRet < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("registerNapi failed unexpectedly: errno %d", -(local variable) const(int) regRetregRet);
return 1;
}
// On success the kernel writes the *previous* config back into `napi`, but
// the values we just registered are the ones that matter for the demo.
void std.stdio.writefln!(char, int, int)(in char[] fmt, int __param_1, int __param_2) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: registered NAPI busy-poll (busy_poll_to=%dus, prefer_busy_poll=%d)",
50, 1);
// Tear the registration back down. `unregisterNapi` fills its argument with
// the configuration that was in effect; we don't need it, so pass a fresh one.
(struct) during.io_uring.io_uring_napiArgument for IORING_REGISTER_NAPI / IORING_UNREGISTER_NAPI. Configures NAPI busy-poll
behaviour for the ring. busy_poll_to is the busy-poll timeout in microseconds.
The struct has a backward-compatible layout: zero-initialised callers leave opcode and
op_param at 0, selecting the original register/unregister behaviour. Linux 6.18+ users
can write IO_URING_NAPI_STATIC_* into opcode to manage the static NAPI tracking list.
Note
Available from Linux 6.9 (extended in 6.18 with opcode/op_param)
io_uring_napi (local variable) during.io_uring.io_uring_napi prevprev;
const (local variable) const(int) unregRetunregRet = (local variable) during.Uring ioio.int during.Uring.unregisterNapi(ref scope during.io_uring.io_uring_napi napi) nothrow @nogc @trustedDisable NAPI busy-polling on the ring. On return, napi is filled with the
configuration that was in effect.
Note
Available from Linux 6.9
unregisterNapi((local variable) during.io_uring.io_uring_napi prevprev);
if ((local variable) const(int) unregRetunregRet < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("unregisterNapi failed: errno %d", -(local variable) const(int) unregRetunregRet);
return 1;
}
return 0;
}