#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_futex_waitv"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — vectored futex wait (`IORING_OP_FUTEX_WAITV`, Linux 6.7).
*
* `FUTEX_WAITV` arms a wait on a *vector* of futexes at once and completes as
* soon as *any one* of them is woken — the async, batched counterpart of the
* legacy `futex_waitv(2)` syscall. This is the building block a runtime uses to
* block a task on several wakeup sources (e.g. several condition words) without
* burning a thread per source.
*
* This example builds an array of two `futex_waitv` entries over two distinct
* 32-bit words, submits a single `prepFutexWaitv(&v[0], 2, 0)`, and spins up a
* helper pthread that wakes the *second* word via the legacy `futex(2)`
* `FUTEX_WAKE_PRIVATE` syscall. The io_uring FUTEX2 waiter and the legacy futex
* share the same kernel hash bucket for a given `uaddr`, so a legacy wake on a
* matching, `FUTEX2_PRIVATE`-armed address wakes the io_uring waiter. We assert
* the completion `res >= 0` (a wake / the index of the woken futex), not an error.
*
* The helper retries every 5 ms (bounded) to defeat the inherent race between
* submission and the kernel actually parking the waiter; the main thread also
* bounds its `wait` with a linked `TIMEOUT` so a misbehaving kernel can't hang.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md
* § "6.7 — Futex, waitid, read-multishot (January 2024)".
*
* Run with: `dub run --single futex-waitv.d`
*
* Portability: if the running kernel has no `io_uring` (too old / blocked by a
* seccomp or container policy), or lacks `FUTEX_WAITV` (kernel < 6.7), the
* program prints a `SKIP:` line and exits 0 so it stays green in CI regardless
* of the host kernel. This box runs 6.18, where the feature is present.
*/
module (module) io_uring_futex_waitvio_uring — vectored futex wait (IORING_OP_FUTEX_WAITV, Linux 6.7).
FUTEX_WAITV arms a wait on a vector of futexes at once and completes as
soon as any one of them is woken — the async, batched counterpart of the
legacy futex_waitv(2) syscall. This is the building block a runtime uses to
block a task on several wakeup sources (e.g. several condition words) without
burning a thread per source.
This example builds an array of two futex_waitv entries over two distinct
32-bit words, submits a single prepFutexWaitv(&v[0], 2, 0), and spins up a
helper pthread that wakes the second word via the legacy futex(2)
FUTEX_WAKE_PRIVATE syscall. The io_uring FUTEX2 waiter and the legacy futex
share the same kernel hash bucket for a given uaddr, so a legacy wake on a
matching, FUTEX2_PRIVATE-armed address wakes the io_uring waiter. We assert
the completion res >= 0 (a wake / the index of the woken futex), not an error.
The helper retries every 5 ms (bounded) to defeat the inherent race between
submission and the kernel actually parking the waiter; the main thread also
bounds its wait with a linked TIMEOUT so a misbehaving kernel can't hang.
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md
§ "6.7 — Futex, waitid, read-multishot (January 2024)".
Run with: dub run --single futex-waitv.d
Portability
if the running kernel has no io_uring (too old / blocked by a
seccomp or container policy), or lacks FUTEX_WAITV (kernel < 6.7), the
program prints a SKIP: line and exits 0 so it stays green in CI regardless
of the host kernel. This box runs 6.18, where the feature is present.
io_uring_futex_waitv;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
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) io_uring_futex_waitv.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
import (package) corecore.(module) core.atomicThe atomic module provides basic support for lock-free
concurrent programming.
Use the -preview=nosharedaccess compiler flag to detect
unsafe individual read or write operations on shared data.
Source
core/atomic.d
Examples
int y = 2;
shared int x = y; // OK
//x++; // read modify write error
x.atomicOp!"+="(1); // OK
//y = x; // read error with preview flag
y = x.atomicLoad(); // OK
assert(y == 3);
//x = 5; // write error with preview flag
x.atomicStore(5); // OK
assert(x.atomicLoad() == 5);
atomic : (alias template) io_uring_futex_waitv.atomicLoad = core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))Loads 'val' from memory and returns it. The memory barrier specified
by 'ms' is applied to the operation, which is fully sequenced by
default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq,
and MemoryOrder.seq.
atomicLoad, (alias template) io_uring_futex_waitv.atomicStore = core.atomic.atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) if (!is(T == shared) && !is(V == shared))Writes 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore, (enum) core.atomic.MemoryOrderSpecifies the memory ordering semantics of an atomic operation.
MemoryOrder;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.errnoD header file for GNU/Linux
errno : (alias constant) io_uring_futex_waitv.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL, (alias constant) io_uring_futex_waitv.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP, (alias constant) io_uring_futex_waitv.ENOSYS = int core.stdc.errno.ENOSYS = 38ENOSYS;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.pthreadD header file for POSIX.
pthread;
import (package) corecore.(package) core.syssys.(package) core.sys.posixposix.(module) core.sys.posix.unistdD header file for POSIX.
unistd : (alias) io_uring_futex_waitv.usleep = int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trustedusleep;
// `SYS_futex` (the legacy futex(2) syscall) is universally available on Linux.
// We use it from the helper thread to issue a `FUTEX_WAKE_PRIVATE`.
version (X86_64X86_64) private enum (constant) int io_uring_futex_waitv.SYS_futex = 202SYS_futex = 202;
else version (X86) private enum SYS_futex = 240;
else version (AArch64) private enum SYS_futex = 98;
else static assert(0, "Unsupported platform for SYS_futex constant");
private enum (constant) int io_uring_futex_waitv.FUTEX_WAKE = 1FUTEX_WAKE = 1;
private enum (constant) int io_uring_futex_waitv.FUTEX_PRIVATE_FLAG = 128FUTEX_PRIVATE_FLAG = 128;
private enum (constant) int io_uring_futex_waitv.FUTEX_WAKE_PRIVATE = 129FUTEX_WAKE_PRIVATE = (constant) int io_uring_futex_waitv.FUTEX_WAKE = 1FUTEX_WAKE | (constant) int io_uring_futex_waitv.FUTEX_PRIVATE_FLAG = 128FUTEX_PRIVATE_FLAG;
private extern (C) int int io_uring_futex_waitv.syscall(int sysno, ...) nothrow @nogc @systemsyscall(int (parameter) int sysnosysno, ...) nothrow @nogc @system;
// Shared state between the main thread and the wake helper.
private struct (struct) io_uring_futex_waitv.WakeCtxWakeCtx
{
uint* (field) uint* io_uring_futex_waitv.WakeCtx.wordword; // address of the futex word the helper will wake
shared int (field) shared(int) io_uring_futex_waitv.WakeCtx.stopstop; // set by main once the CQE has arrived
int (field) int io_uring_futex_waitv.WakeCtx.attemptsattempts; // number of wake attempts the helper made
}
// Helper thread: repeatedly `FUTEX_WAKE_PRIVATE`s `ctx.word` until the main
// thread sets `stop`. Bounded to ~1s of attempts so it can never hang the run.
private extern (C) void* void* io_uring_futex_waitv.wakeWorker(void* arg) nothrow @nogc @systemwakeWorker(void* (parameter) void* argarg) @system nothrow @nogc
{
auto (local variable) io_uring_futex_waitv.WakeCtx* ctxctx = cast((struct) io_uring_futex_waitv.WakeCtxWakeCtx*) (parameter) void* argarg;
foreach ((local variable) int ii; 0 .. 200) // up to ~1s (200 * 5ms)
{
// Re-check before sleeping so we exit promptly once main signals stop.
if (int core.atomic.atomicLoad!(MemoryOrder.acq, int)(ref return scope shared(const(int)) val) pure nothrow @nogc @trustedLoads 'val' from memory and returns it. The memory barrier specified
by 'ms' is applied to the operation, which is fully sequenced by
default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq,
and MemoryOrder.seq.
atomicLoad!((enum) core.atomic.MemoryOrderSpecifies the memory ordering semantics of an atomic operation.
MemoryOrder.(enum value) core.atomic.MemoryOrder.acq = 2Hoist-load + hoist-store barrier.
Corresponds to LLVM AtomicOrdering.Acquire
and C++11/C11 memory_order_acquire.
acq)((local variable) io_uring_futex_waitv.WakeCtx* ctxctx.(field) shared(int) io_uring_futex_waitv.WakeCtx.stopstop)) break;
int core.sys.posix.unistd.usleep(uint) nothrow @nogc @trustedusleep(5_000); // 5ms
(local variable) io_uring_futex_waitv.WakeCtx* ctxctx.(field) int io_uring_futex_waitv.WakeCtx.attemptsattempts = (local variable) int ii + 1;
// arg2 = val = 1 => wake at most one waiter on this address.
int io_uring_futex_waitv.syscall(int sysno, ...) nothrow @nogc @systemsyscall((constant) int io_uring_futex_waitv.SYS_futex = 202SYS_futex, cast(void*) (local variable) io_uring_futex_waitv.WakeCtx* ctxctx.(field) uint* io_uring_futex_waitv.WakeCtx.wordword, (constant) int io_uring_futex_waitv.FUTEX_WAKE_PRIVATE = 129FUTEX_WAKE_PRIVATE, 1, null, null, 0);
}
return null;
}
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;
}
// Two distinct futex words. Both armed with val=0, matching their current
// value, so the wait genuinely parks (a mismatch would return -EAGAIN).
uint[2] (local variable) uint[2] wordswords = [0, 0];
// The vector of futexes to wait on. FUTEX2_SIZE_U32 selects a 32-bit futex
// (the only size most arches support); FUTEX2_PRIVATE puts it in the
// process-private hash so a legacy FUTEX_WAKE_PRIVATE on the same address
// can match it.
(struct) during.io_uring.futex_waitvSingle entry passed to IORING_OP_FUTEX_WAITV. Mirrors struct futex_waitv`` from
<linux/futex.h>.
Note
Available from Linux 6.7
futex_waitv[2] (local variable) during.io_uring.futex_waitv[2] vecvec;
foreach ((local variable) int ii; 0 .. 2)
{
(local variable) during.io_uring.futex_waitv[2] vecvec[(local variable) int ii].(field) ulong during.io_uring.futex_waitv.valexpected value of the futex
val = 0; // expected value — must equal *uaddr for the wait to arm
(local variable) during.io_uring.futex_waitv[2] vecvec[(local variable) int ii].(field) ulong during.io_uring.futex_waitv.uaddrpointer to the futex word
uaddr = cast(ulong) &(local variable) uint[2] wordswords[(local variable) int ii];
(local variable) during.io_uring.futex_waitv[2] vecvec[(local variable) int ii].(field) uint during.io_uring.futex_waitv.flagsFUTEX2_SIZE_* (+ FUTEX2_PRIVATE / FUTEX2_NUMA)
flags = (constant) int during.io_uring.FUTEX2_SIZE_U32 = 232-bit futex (the only size supported on most arches today)
FUTEX2_SIZE_U32 | (constant) int during.io_uring.FUTEX2_PRIVATE = 128process-private (skips NUMA hash lookup)
FUTEX2_PRIVATE;
}
// Spin up the helper that will wake the *second* word. FUTEX_WAITV completes
// when ANY entry is woken, so waking words[1] must complete the whole wait.
(struct) io_uring_futex_waitv.WakeCtxWakeCtx (local variable) io_uring_futex_waitv.WakeCtx ctxctx;
(local variable) io_uring_futex_waitv.WakeCtx ctxctx.(field) uint* io_uring_futex_waitv.WakeCtx.wordword = &(local variable) uint[2] wordswords[1];
(alias) core.sys.posix.sys.types.pthread_t = ulongpthread_t (local variable) ulong tidtid;
const (local variable) const(int) prpr = int core.sys.posix.pthread.pthread_create(ulong*, scope const(core.sys.posix.sys.types.pthread_attr_t*), extern (C) void* function(void*), void*) nothrow @nogcpthread_create(&(local variable) ulong tidtid, null, &void* io_uring_futex_waitv.wakeWorker(void* arg) nothrow @nogc @systemwakeWorker, &(local variable) io_uring_futex_waitv.WakeCtx ctxctx);
if ((local variable) const(int) prpr != 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("pthread_create failed: errno %d", (local variable) const(int) prpr);
return 1;
}
// Submit the vectored wait, linked to a TIMEOUT so the ring is never blocked
// indefinitely: if the wake never lands, the TIMEOUT fires and unblocks
// `wait`, and the wait CQE comes back -ECANCELED. IO_LINK means the timeout
// is the deadline for the preceding waitv.
enum ulong (constant) ulong io_uring_futex_waitv.main.WAITV_DATA = 61566LUWAITV_DATA = 0xF07E;
enum ulong (constant) ulong io_uring_futex_waitv.main.TIMEOUT_DATA = 29152LUTIMEOUT_DATA = 0x71_E0;
(local variable) during.Uring ioio.putWith!(
(ref SubmissionEntry e, futex_waitv* v)
{
e.prepFutexWaitv(v, 2, 0); // wait on both entries; wake on any
e.user_data = WAITV_DATA;
e.flags |= SubmissionEntryFlags.IO_LINK;
})(&during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, during.io_uring.futex_waitv* v) nothrow @nogc @safe
{
prepFutexWaitv(e, cast(const(during.io_uring.futex_waitv)*)v, 2u, 0u);
e.user_data = 61566LU;
cast(int)e.flags |= 4;
}
, during.io_uring.futex_waitv*)(during.io_uring.futex_waitv* __param_0) nothrow @nogc return ref @safeAdds 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().
vec[0]);
// 1.5s ceiling — comfortably above the helper's first 5ms attempt, well
// under the 2s budget. KernelTimespec is {tv_sec, tv_nsec}.
(struct) during.io_uring.KernelTimespecTime specification as defined in kernel headers (used by TIMEOUT operations)
KernelTimespec (local variable) during.io_uring.KernelTimespec tsts = { tv_sec: 1, tv_nsec: 500_000_000 };
(local variable) during.Uring ioio.putWith!(
(ref SubmissionEntry e, KernelTimespec* t)
{
e.prepTimeout(*t, 0, TimeoutFlags.REL);
e.user_data = TIMEOUT_DATA;
})(&during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, during.io_uring.KernelTimespec* t) nothrow @nogc @safe
{
prepTimeout(e, *t, 0LU, TimeoutFlags.REL);
e.user_data = 29152LU;
}
, during.io_uring.KernelTimespec*)(during.io_uring.KernelTimespec* __param_0) nothrow @nogc return ref @safeAdds 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().
ts);
const (local variable) const(int) submittedsubmitted = (local variable) during.Uring ioio.int during.Uring.submit(uint want) nothrow @nogc @safeSubmits qued SubmissionEntry to be processed by kernel.
submit(0);
if ((local variable) const(int) submittedsubmitted < 0)
{
void core.atomic.atomicStore!(MemoryOrder.rel, int, int)(ref shared(int) val, int newval) pure nothrow @nogc @trustedWrites 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore!((enum) core.atomic.MemoryOrderSpecifies the memory ordering semantics of an atomic operation.
MemoryOrder.(enum value) core.atomic.MemoryOrder.rel = 3Sink-load + sink-store barrier.
Corresponds to LLVM AtomicOrdering.Release
and C++11/C11 memory_order_release.
rel)((local variable) io_uring_futex_waitv.WakeCtx ctxctx.(field) shared(int) io_uring_futex_waitv.WakeCtx.stopstop, 1);
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogcpthread_join((local variable) ulong tidtid, null);
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit failed: errno %d", -(local variable) const(int) submittedsubmitted);
return 1;
}
// Drain both completions (the waitv and the linked timeout). We need at most
// two CQEs; capture the waitv result by user_data.
int (local variable) int waitvReswaitvRes = int.(constant) int int.min = -2147483648min;
bool (local variable) bool sawWaitvsawWaitv = false;
foreach ((local variable) int __; 0 .. 2)
{
(local variable) during.Uring ioio.int during.Uring.wait(uint want = 1u) nothrow @nogcSimmilar to submit but with this method we just wait for required number
of CompletionEntries.
wait(1);
const (local variable) const(during.io_uring.CompletionEntry) cqecqe = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front;
if ((local variable) const(during.io_uring.CompletionEntry) cqecqe.(field) ulong during.io_uring.CompletionEntry.user_datasqe->data submission passed back
user_data == (constant) ulong io_uring_futex_waitv.main.WAITV_DATA = 61566LUWAITV_DATA)
{
(local variable) int waitvReswaitvRes = (local variable) const(during.io_uring.CompletionEntry) cqecqe.(field) int during.io_uring.CompletionEntry.resresult code for this event
res;
(local variable) bool sawWaitvsawWaitv = true;
}
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
if ((local variable) bool sawWaitvsawWaitv) break; // got what we came for; the timeout CQE (if any) can drain on exit
}
// Tell the helper to stop and join it before inspecting results.
void core.atomic.atomicStore!(MemoryOrder.rel, int, int)(ref shared(int) val, int newval) pure nothrow @nogc @trustedWrites 'newval' into 'val'. The memory barrier specified by 'ms' is
applied to the operation, which is fully sequenced by default.
Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and
MemoryOrder.seq.
atomicStore!((enum) core.atomic.MemoryOrderSpecifies the memory ordering semantics of an atomic operation.
MemoryOrder.(enum value) core.atomic.MemoryOrder.rel = 3Sink-load + sink-store barrier.
Corresponds to LLVM AtomicOrdering.Release
and C++11/C11 memory_order_release.
rel)((local variable) io_uring_futex_waitv.WakeCtx ctxctx.(field) shared(int) io_uring_futex_waitv.WakeCtx.stopstop, 1);
int core.sys.posix.pthread.pthread_join(ulong, void**) nothrow @nogcpthread_join((local variable) ulong tidtid, null);
if (!(local variable) bool sawWaitvsawWaitv)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("no FUTEX_WAITV completion observed");
return 1;
}
// -EINVAL / -EOPNOTSUPP / -ENOSYS => the op isn't supported on this kernel.
if ((local variable) int waitvReswaitvRes == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (local variable) int waitvReswaitvRes == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP || (local variable) int waitvReswaitvRes == -(constant) int core.stdc.errno.ENOSYS = 38ENOSYS)
{
void std.stdio.writefln!(char, int)(in char[] fmt, int __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: IORING_OP_FUTEX_WAITV unsupported on this kernel (res=%d)", (local variable) int waitvReswaitvRes);
return 0;
}
// A successful wake reports res >= 0 (the kernel returns the index of the
// woken futex). A negative value here means the wait was cancelled by the
// timeout (the helper never managed to wake it) or another genuine failure.
if ((local variable) int waitvReswaitvRes < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("FUTEX_WAITV was not woken: res=%d (errno %d), helper made %d attempts",
(local variable) int waitvReswaitvRes, -(local variable) int waitvReswaitvRes, (local variable) io_uring_futex_waitv.WakeCtx ctxctx.(field) int io_uring_futex_waitv.WakeCtx.attemptsattempts);
return 1;
}
void std.stdio.writefln!(char, int, int)(in char[] fmt, int __param_1, int __param_2) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: FUTEX_WAITV woken (res=%d, woken futex index) after %d helper wake attempt(s)",
(local variable) int waitvReswaitvRes, (local variable) io_uring_futex_waitv.WakeCtx ctxctx.(field) int io_uring_futex_waitv.WakeCtx.attemptsattempts);
return 0;
}