napi.dhover×52all
#!/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_example

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.

napi_example
;
import
(module) during

Simple idiomatic dlang wrapper around linux io_uring (see: https://kernel.dk/io_uring.pdf) asynchronous API.

during
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.linux
linux
.
(module) core.sys.linux.errno

D header file for GNU/Linux

glibc stdlib/errno.h

errno
:
(alias constant) napi_example.EINVAL = int core.stdc.errno.EINVAL = 22
EINVAL
,
(alias constant) napi_example.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95
EOPNOTSUPP
,
(alias constant) napi_example.ENOSYS = int core.stdc.errno.ENOSYS = 38
ENOSYS
;
import
(package) std
std
.
(module) std.stdio
Category 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:

  1. The lowest layer is the operating system layer. The two main schemes are Windows and Posix.

  2. C's stdio.h which unifies the two operating system schemes.

  3. std.stdio, this module, unifies the various stdio.h implementations into a high level package for D programs.

Source

std/stdio.d

@copyrightCopyright The D Language Foundation 2007-.@licenseBoost License 1.0.@authorsWalter Bright, Andrei Alexandrescu, Alex Rønne Petersen
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.Uring

Main 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 io
io
;
const
(local variable) const(int) setupRet
setupRet
=
(local variable) during.Uring io
io
.
int during.setup(ref during.Uring uring, uint entries = 128u, during.io_uring.SetupFlags flags = SetupFlags.NONE) nothrow @nogc @safe

Setup new instance of io_uring into provided Uring structure.

@paramuring Uring structure to be initialized (must not be already initialized)@paramentries Number of entries to initialize uring with@paramflags SetupFlags to use to initialize uring.@returnsOn succes it returns 0, -errno otherwise.
setup
(8);
if (
(local variable) const(int) setupRet
setupRet
< 0)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safe

Equivalent to writef(fmt, args, '\n').

writefln
("SKIP: io_uring_setup failed (errno %d) — io_uring unavailable on this host", -
(local variable) const(int) setupRet
setupRet
);
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_napi

Argument 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 napi
napi
;
(local variable) during.io_uring.io_uring_napi napi
napi
.
(field) uint during.io_uring.io_uring_napi.busy_poll_to

busy-poll timeout in microseconds

busy_poll_to
= 50; // busy-poll timeout, microseconds
(local variable) during.io_uring.io_uring_napi napi
napi
.
(field) ubyte during.io_uring.io_uring_napi.prefer_busy_poll

boolean: prefer busy polling over interrupts

prefer_busy_poll
= 1; // prefer busy poll over interrupts
const
(local variable) const(int) regRet
regRet
=
(local variable) during.Uring io
io
.
int during.Uring.registerNapi(ref scope during.io_uring.io_uring_napi napi) nothrow @nogc @trusted

Enable 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 napi
napi
);
if (
(local variable) const(int) regRet
regRet
== -
(constant) int core.stdc.errno.EINVAL = 22
EINVAL
||
(local variable) const(int) regRet
regRet
== -
(constant) int core.stdc.errno.EOPNOTSUPP = 95
EOPNOTSUPP
||
(local variable) const(int) regRet
regRet
== -
(constant) int core.stdc.errno.ENOSYS = 38
ENOSYS
)
{ // 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) @safe

Equivalent 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) regRet
regRet
);
return 0; } if (
(local variable) const(int) regRet
regRet
< 0)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("registerNapi failed unexpectedly: errno %d", -
(local variable) const(int) regRet
regRet
);
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) @safe

Equivalent 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_napi

Argument 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 prev
prev
;
const
(local variable) const(int) unregRet
unregRet
=
(local variable) during.Uring io
io
.
int during.Uring.unregisterNapi(ref scope during.io_uring.io_uring_napi napi) nothrow @nogc @trusted

Disable 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 prev
prev
);
if (
(local variable) const(int) unregRet
unregRet
< 0)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("unregisterNapi failed: errno %d", -
(local variable) const(int) unregRet
unregRet
);
return 1; } return 0; }