poll-add.dhover×78all
#!/usr/bin/env dub
/+ dub.sdl:
    name "io_uring_poll_add"
    dependency "during" version="~>0.5.0"
    platforms "linux"
    targetPath "build"
+/
/**
 * `io_uring` — readiness notification with `IORING_OP_POLL_ADD` (Linux 5.1).
 *
 * `POLL_ADD` is io_uring's answer to `poll(2)`/`epoll`: arm a one-shot
 * readiness watch on a file descriptor and get a CQE when it becomes ready,
 * without ever calling `poll` from userspace. It is the building block for
 * event-loop style I/O — the kernel does the waiting and notifies you through
 * the same completion ring every other op uses.
 *
 * This example creates a libc `pipe()`, arms a `POLL_ADD` for `POLLIN` on the
 * read end, then writes one byte into the write end. Once the read end is
 * readable the CQE fires, and (per during's `tests/poll.d`) `res` carries the
 * set of ready poll events — a positive value with the `POLLIN` bit set.
 *
 * Companion to the io_uring chronology:
 * see docs/research/async-io/io-uring/timeline.md § "5.1 — The introduction (May 2019)".
 *
 * Run with: `dub run --single poll-add.d`
 *
 * Portability: `POLL_ADD` has existed since the 5.1 introduction, so it works on
 * any kernel that has io_uring at all. If io_uring is unavailable (too old, or
 * blocked by a seccomp/container policy) the program prints a `SKIP:` line and
 * exits 0 so it stays green in CI regardless of the host kernel.
 */
module 
(module) io_uring_poll_add

io_uring — readiness notification with IORING_OP_POLL_ADD (Linux 5.1).

POLL_ADD is io_uring's answer to poll(2)/epoll: arm a one-shot readiness watch on a file descriptor and get a CQE when it becomes ready, without ever calling poll from userspace. It is the building block for event-loop style I/O — the kernel does the waiting and notifies you through the same completion ring every other op uses.

This example creates a libc pipe(), arms a POLL_ADD for POLLIN on the read end, then writes one byte into the write end. Once the read end is readable the CQE fires, and (per during's tests/poll.d) res carries the set of ready poll events — a positive value with the POLLIN bit set.

Companion to the io_uring chronology: see docs/research/async-io/io-uring/timeline.md § "5.1 — The introduction (May 2019)".

Run with: dub run --single poll-add.d

Portability

POLL_ADD has existed since the 5.1 introduction, so it works on any kernel that has io_uring at all. If io_uring is unavailable (too old, or blocked by a seccomp/container policy) the program prints a SKIP: line and exits 0 so it stays green in CI regardless of the host kernel.

io_uring_poll_add
;
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.posix
posix
.
(module) core.sys.posix.poll

D header file for POSIX.

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0.@authorsSean Kelly@standardsThe Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
poll
:
(alias enum value) io_uring_poll_add.POLLIN = core.sys.posix.poll.POLLIN = 1
POLLIN
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(module) core.sys.posix.unistd

D header file for POSIX.

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0.@authorsSean Kelly@standardsThe Open Group Base Specifications Issue 8, IEEE Std 1003.1, 2024 Edition
unistd
:
(alias) io_uring_poll_add.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
,
(alias) io_uring_poll_add.pipe = int core.sys.posix.unistd.pipe(ref int[2]) nothrow @nogc @trusted
pipe
,
(alias) io_uring_poll_add.write = long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogc
write
;
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
: stderr,
(alias template) io_uring_poll_add.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))

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

writefln
,
(alias template) io_uring_poll_add.writeln = std.stdio.writeln(T...)(T args)

Equivalent to write(args, '\n'). Calling writeln without arguments is valid and just prints a newline to the standard output.

Example

Reads stdin and writes it to stdout with an argument counter.

import std.stdio;

void main()
{
    string line;

    for (size_t count = 0; (line = readln) !is null; count++)
    {
         writeln("Input ", count, ": ", line);
    }
}
@paramargs the items to write to stdout@throwsIn case of an I/O error, throws an StdioException.
writeln
;
int
int D main()
main
()
{ // user_data cookie identifying this poll op when its CQE comes back. enum ulong
(constant) ulong io_uring_poll_add.main.cookie = 1LU
cookie
= 1;
(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; } // A classic anonymous pipe: fds[0] is the read end, fds[1] the write end. int[2]
(local variable) int[2] fds
fds
;
if (
int core.sys.posix.unistd.pipe(ref int[2]) nothrow @nogc @trusted
pipe
(
(local variable) int[2] fds
fds
) != 0)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writeln
("SKIP: pipe() failed");
return 0; } scope (exit) {
int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
(
(local variable) int[2] fds
fds
[0]);
int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
(
(local variable) int[2] fds
fds
[1]);
} // Arm a one-shot poll on the read end for readability (POLLIN). The kernel // will park this until the fd is readable, then post a CQE.
(local variable) during.Uring io
io
.putWith!((ref SubmissionEntry e, int readFd) {
e.prepPollAdd(readFd, PollEvents.IN); e.user_data = cookie; })(
during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, int readFd) nothrow @nogc @safe { prepPollAdd(e, readFd, PollEvents.IN, PollFlags.NONE); e.user_data = 1LU; } , int)(ref int __param_0) nothrow @nogc return ref @safe

Adds new entry to the SubmissionQueue.

Note that this just adds entry to the queue and doesn't advance the tail marker kernel sees. For that finishSq() is needed to be called next.

Also note that to actually enter new entries to kernel, it's needed to call submit().

@paramFN Function to fill next entry in queue by ref (should be faster). It is expected to be in a form of void function(ARGS)(ref SubmissionEntry, auto ref ARGS). Note that in this case queue entry is cleaned first before function is called.@paramentry Custom built SubmissionEntry to be posted as is. Note that in this case it is copied whole over one in the SubmissionQueue.@paramargs Optional arguments passed to the function@returnsreference to Uring structure so it's possible to chain multiple commands.
fds
[0]);
// submit(0) flushes the SQ without blocking on a completion count — the poll // is now armed in the kernel but the fd is not yet readable. const
(local variable) const(int) submitted
submitted
=
(local variable) during.Uring io
io
.
int during.Uring.submit(uint want) nothrow @nogc @safe

Submits qued SubmissionEntry to be processed by kernel.

@paramwant number of CompletionEntries to wait for. If 0, this just submits queued entries and returns. If > 0, it blocks until at least wanted number of entries were completed.@paramsig See io_uring_enter(2) man page@returnsNumber of submitted entries on success, -errno on error
submit
(0);
if (
(local variable) const(int) submitted
submitted
< 0)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("submit failed: errno %d", -
(local variable) const(int) submitted
submitted
);
return 1; } // Make the read end readable: one byte into the write end is enough to // satisfy POLLIN and trip the armed poll. immutable ubyte
(local variable) immutable(ubyte) one
one
= 0x2A;
const
(local variable) const(long) wrote
wrote
=
long core.sys.posix.unistd.write(int, scope const(void*), ulong) nothrow @nogc
write
(
(local variable) int[2] fds
fds
[1], &
(local variable) immutable(ubyte) one
one
, 1);
if (
(local variable) const(long) wrote
wrote
!= 1)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("write to pipe failed (ret %d)",
(local variable) const(long) wrote
wrote
);
return 1; } // Block for the poll completion.
(local variable) during.Uring io
io
.
int during.Uring.wait(uint want = 1u) nothrow @nogc

Simmilar to submit but with this method we just wait for required number of CompletionEntries.

@returns0 on success, -errno on error
wait
(1);
const
(local variable) const(int) res
res
=
(local variable) during.Uring io
io
.
during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safe

Get first CompletionEntry from cq ring

front
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
;
const
(local variable) const(ulong) echoed
echoed
=
(local variable) during.Uring io
io
.
during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safe

Get first CompletionEntry from cq ring

front
.
(field) ulong during.io_uring.CompletionEntry.user_data

sqe->data submission passed back

user_data
;
(local variable) during.Uring io
io
.
void during.Uring.popFront() pure nothrow @nogc @safe

Move to next CompletionEntry

popFront
();
if (
(local variable) const(int) res
res
< 0)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("POLL_ADD completed with error: errno %d", -
(local variable) const(int) res
res
);
return 1; } if (
(local variable) const(ulong) echoed
echoed
!=
(constant) ulong io_uring_poll_add.main.cookie = 1LU
cookie
)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("user_data mismatch: expected %d, got %d",
(constant) ulong io_uring_poll_add.main.cookie = 1LU
cookie
,
(local variable) const(ulong) echoed
echoed
);
return 1; } // On success `res` is the bitmask of ready events; POLLIN must be set since // the pipe's read end now has data. if (!(
(local variable) const(int) res
res
&
(enum value) core.sys.posix.poll.POLLIN = 1
POLLIN
))
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("expected POLLIN in ready mask, got 0x%X",
(local variable) const(int) res
res
);
return 1; }
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safe

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

writefln
("ok: POLL_ADD reported readiness (res=0x%X, POLLIN set) on the pipe read end",
(local variable) const(int) res
res
);
return 0; }