sq-rewind.dhover×91all
#!/usr/bin/env dub
/+ dub.sdl:
    name "io_uring_sq_rewind"
    dependency "during" version="~>0.5.0"
    platforms "linux"
    targetPath "build"
+/
/**
 * `io_uring` — SQ rewind (`IORING_SETUP_SQ_REWIND`, Linux 7.0).
 *
 * Normally the submission-queue tail only moves forward, and once the kernel has
 * been told (via the shared `tail` index) about a batch of SQEs it owns them —
 * any it can't process in one `io_uring_enter` would simply be lost to the
 * application. `IORING_SETUP_SQ_REWIND` changes that: when the kernel stops a
 * batch part-way (e.g. it hit a malformed SQE), it *rewinds its head* back over
 * the SQEs it did not consume, so the application can re-submit them. This makes
 * partial submits recoverable and enables speculative batching where a program
 * prepares entries optimistically and re-drives whatever the kernel didn't take.
 *
 * The flag requires `IORING_SETUP_NO_SQARRAY` (head/tail index the SQE array
 * directly, so there is a well-defined tail to rewind) and is incompatible with
 * `SQPOLL` (a kernel poll thread could consume an SQE out from under a rewind).
 *
 * What this program demonstrates (when the kernel supports the flag):
 *   1. queue three NOPs, deliberately corrupting the *middle* one (bogus opcode);
 *   2. `submit()` — the kernel processes SQE #1, chokes on the malformed #2, and
 *      stops the batch early, rewinding its head over the un-consumed #2 and #3;
 *   3. drain the completion(s) from that partial submit;
 *   4. `submit()` again with no fresh `putWith` — on a rewind ring the trailing
 *      good NOP (#3) survived and is re-sent, and we assert its `user_data`
 *      round-trips. On a non-rewind ring SQE #3 would have been dropped, so this
 *      observably exercises the rewind machinery rather than a plain NOP.
 *
 * Companion to the io_uring chronology:
 * see docs/research/async-io/io-uring/timeline.md § "7.0 — SQ rewind".
 *
 * Run with: `dub run --single sq-rewind.d`
 *
 * Portability: this box runs Linux 6.18, which predates `SQ_REWIND` (Linux 7.0),
 * so `io_uring_setup` is expected to reject the flag with `-EINVAL`; we print a
 * `SKIP:` line and exit 0. The same SKIP path covers hosts with no `io_uring`
 * at all (too old, or blocked by a seccomp/container policy).
 */
module 
(module) io_uring_sq_rewind

io_uring — SQ rewind (IORING_SETUP_SQ_REWIND, Linux 7.0).

Normally the submission-queue tail only moves forward, and once the kernel has been told (via the shared tail index) about a batch of SQEs it owns them — any it can't process in one io_uring_enter would simply be lost to the application. IORING_SETUP_SQ_REWIND changes that: when the kernel stops a batch part-way (e.g. it hit a malformed SQE), it rewinds its head back over the SQEs it did not consume, so the application can re-submit them. This makes partial submits recoverable and enables speculative batching where a program prepares entries optimistically and re-drives whatever the kernel didn't take.

The flag requires IORING_SETUP_NO_SQARRAY (head/tail index the SQE array directly, so there is a well-defined tail to rewind) and is incompatible with SQPOLL (a kernel poll thread could consume an SQE out from under a rewind).

What this program demonstrates (when the kernel supports the flag):

  1. queue three NOPs, deliberately corrupting the middle one (bogus opcode);

  2. submit() — the kernel processes SQE #1, chokes on the malformed #2, and stops the batch early, rewinding its head over the un-consumed #2 and #3;

  3. drain the completion(s) from that partial submit;

  4. submit() again with no fresh putWith — on a rewind ring the trailing good NOP (#3) survived and is re-sent, and we assert its user_data round-trips. On a non-rewind ring SQE #3 would have been dropped, so this observably exercises the rewind machinery rather than a plain NOP.

Companion to the io_uring chronology: see docs/research/async-io/io-uring/timeline.md § "7.0 — SQ rewind".

Run with: dub run --single sq-rewind.d

Portability

this box runs Linux 6.18, which predates SQ_REWIND (Linux 7.0), so io_uring_setup is expected to reject the flag with -EINVAL; we print a SKIP: line and exit 0. The same SKIP path covers hosts with no io_uring at all (too old, or blocked by a seccomp/container policy).

io_uring_sq_rewind
;
import
(module) during

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

during
;
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) io_uring_sq_rewind.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
()
{ import
(package) core
core
.
(package) core.stdc
stdc
.
(module) core.stdc.errno

D header file for C99.

pubs.opengroup.org/onlinepubs/009695399/basedefs/errno.h.html, errno.h

Source

core/stdc/errno.d

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseDistributed under the Boost Software License 1.0. (See accompanying file LICENSE)@authorsSean Kelly, Alex Rønne Petersen@standardsISO/IEC 9899:1999 (E)
errno
:
(alias constant) EINVAL = int core.stdc.errno.EINVAL = 22
EINVAL
,
(alias constant) EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95
EOPNOTSUPP
,
(alias constant) ENOSYS = int core.stdc.errno.ENOSYS = 38
ENOSYS
,
(alias constant) EPERM = int core.stdc.errno.EPERM = 1
EPERM
;
(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
;
// SQ_REWIND requires NO_SQARRAY (the direct head/tail layout that gives a // rewindable tail) and is mutually exclusive with SQPOLL. 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,
(enum) during.io_uring.SetupFlags

io_uring_setup() flags

SetupFlags
.
(enum value) during.io_uring.SetupFlags.NO_SQARRAY = 65536u

IORING_SETUP_NO_SQARRAY (from Linux 6.6)

Skip the indirect SQ array — head/tail now index the SQE array directly. Saves a small mmap region. Default for newly-created rings on recent kernels.

NO_SQARRAY
|
(enum) during.io_uring.SetupFlags

io_uring_setup() flags

SetupFlags
.
(enum value) during.io_uring.SetupFlags.SQ_REWIND = 1048576u

IORING_SETUP_SQ_REWIND (from Linux 6.18)

Requires NO_SQARRAY and is incompatible with SQPOLL. Lets the application rewind the SQ tail to retry SQEs that have not yet been processed.

SQ_REWIND
);
if (
(local variable) const(int) setupRet
setupRet
< 0)
{ const
(local variable) const(int) e
e
= -
(local variable) const(int) setupRet
setupRet
;
// -EINVAL / -EOPNOTSUPP / -ENOSYS: the kernel doesn't know this flag // (e.g. < 7.0, like this 6.18 box). -EPERM: policy-blocked. if (
(local variable) const(int) e
e
==
(constant) int core.stdc.errno.EINVAL = 22
EINVAL
||
(local variable) const(int) e
e
==
(constant) int core.stdc.errno.EOPNOTSUPP = 95
EOPNOTSUPP
||
(local variable) const(int) e
e
==
(constant) int core.stdc.errno.ENOSYS = 38
ENOSYS
||
(local variable) const(int) e
e
==
(constant) int core.stdc.errno.EPERM = 1
EPERM
)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safe

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

writefln
("SKIP: IORING_SETUP_SQ_REWIND unsupported (errno %d) — needs Linux 7.0+",
(local variable) const(int) e
e
);
return 0; } stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("io_uring_setup(NO_SQARRAY|SQ_REWIND) failed unexpectedly: errno %d",
(local variable) const(int) e
e
);
return 1; } // Three NOPs; the middle one is sabotaged with a bogus opcode so the kernel // refuses it and halts the batch there. The trailing good NOP (user_data 3) // is the one whose survival proves the rewind behaviour. enum ulong
(constant) ulong io_uring_sq_rewind.main.goodHead = 1LU
goodHead
= 1,
(constant) ulong io_uring_sq_rewind.main.badMiddle = 2LU
badMiddle
= 2,
(constant) ulong io_uring_sq_rewind.main.goodTail = 3LU
goodTail
= 3;
(local variable) during.Uring io
io
.putWith!((ref SubmissionEntry e) { e.prepNop(); e.user_data = goodHead; })();
(local variable) during.Uring io
io
.putWith!((ref SubmissionEntry e) {
e.prepNop(); e.opcode = cast(Operation) 0xFF; // not a real op — kernel rejects it e.user_data = badMiddle; })();
(local variable) during.Uring io
io
.putWith!((ref SubmissionEntry e) { e.prepNop(); e.user_data = goodTail; })();
// First submit: the kernel takes the leading good NOP, then stops at the // malformed one. It must report a partial count (>0, <3) and rewind its head // over the SQEs it did not consume. const
(local variable) const(int) first
first
=
(local variable) during.Uring io
io
.
int during.Uring.submit() 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
();
if (
(local variable) const(int) first
first
< 0)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("first submit failed: errno %d", -
(local variable) const(int) first
first
);
return 1; } if (!(
(local variable) const(int) first
first
> 0 &&
(local variable) const(int) first
first
< 3))
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("expected a partial submit (1 or 2), got %d",
(local variable) const(int) first
first
);
return 1; } // Drain everything that completed from the partial submit (bounded: the // kernel produced exactly `first` CQEs and they are already imminent).
(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);
while (!
(local variable) during.Uring io
io
.
bool during.Uring.empty() const pure nothrow @nogc @safe

Check if there is some CompletionEntry to process.

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

Move to next CompletionEntry

popFront
();
// Second submit with NO new SQE queued. On a SQ_REWIND ring the un-consumed // trailing NOP was rewound and re-presented, so this sends exactly one SQE. // Without rewind support `submit()` would have nothing left to send here. const
(local variable) const(int) second
second
=
(local variable) during.Uring io
io
.
int during.Uring.submit() 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
();
if (
(local variable) const(int) second
second
< 0)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("re-submit of rewound SQE failed: errno %d", -
(local variable) const(int) second
second
);
return 1; } if (
(local variable) const(int) second
second
!= 1)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("rewound SQE was lost: re-submit sent %d entries, expected 1",
(local variable) const(int) second
second
);
return 1; } // The survivor completes; its cookie confirms it is exactly the SQE the // kernel rewound rather than something freshly minted.
(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
("rewound NOP completed with error: errno %d", -
(local variable) const(int) res
res
);
return 1; } if (
(local variable) const(ulong) echoed
echoed
!=
(constant) ulong io_uring_sq_rewind.main.goodTail = 3LU
goodTail
)
{ stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("rewound SQE mismatch: expected user_data %d, got %d",
(constant) ulong io_uring_sq_rewind.main.goodTail = 3LU
goodTail
,
(local variable) const(ulong) echoed
echoed
);
return 1; }
void std.stdio.writefln!(char, const(int), const(ulong))(in char[] fmt, const(int) __param_1, const(ulong) __param_2) @safe

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

writefln
("ok: SQ_REWIND recovered the trailing SQE after a partial submit "
~ "(first sent %d, leftover user_data %d re-submitted and completed)",
(local variable) const(int) first
first
,
(local variable) const(ulong) echoed
echoed
);
return 0; }