send-zc.dhover×223all
#!/usr/bin/env dub
/+ dub.sdl:
    name "io_uring_send_zc"
    dependency "during" version="~>0.5.0"
    platforms "linux"
    targetPath "build"
+/
/**
 * `io_uring` — zero-copy send (`IORING_OP_SEND_ZC`, Linux 6.0).
 *
 * Ordinary `IORING_OP_SEND` copies the payload into the socket buffer before
 * the CQE arrives, so the user buffer is free to reuse immediately. Zero-copy
 * send instead pins the user pages and lets the NIC/loopback DMA straight from
 * them — which means the buffer must stay alive until the *kernel* is done with
 * it, not just until the send is "issued". `io_uring` signals this with a
 * distinctive **two-CQE pattern** for a single `SEND_ZC` SQE:
 *
 *   1. a transfer-result CQE carrying the byte count, flagged `CQEFlags.MORE`
 *      ("more completions for this user_data are coming"); and
 *   2. a later notification CQE flagged `CQEFlags.NOTIF` — emitted once the
 *      kernel has released the buffer, so it is now safe to reuse/free.
 *
 * Passing `IORING_SEND_ZC_REPORT_USAGE` additionally makes the kernel report,
 * in the notification CQE's `res`, whether the path was truly zero-copy or it
 * had to fall back to a copy (`IORING_NOTIF_USAGE_ZC_COPIED`). On loopback the
 * kernel often copies — that is expected and still a successful demonstration
 * of the API and its completion sequence.
 *
 * This example connects a client socket to a listening server, both on
 * 127.0.0.1, sends a payload from the client with `SEND_ZC`, asserts the
 * MORE-then-NOTIF CQE sequence, and reads the bytes back on the server end to
 * confirm the transfer.
 *
 * Companion to the io_uring chronology:
 * see docs/research/async-io/io-uring/timeline.md
 * § "6.0 — Zero-copy send, single-issuer, sync cancel (October 2022)".
 *
 * Run with: `dub run --single send-zc.d`
 *
 * Portability: if `io_uring` is unavailable (old kernel / sandbox) or the
 * kernel predates `SEND_ZC` (< 6.0), the program prints a `SKIP:` line and
 * exits 0 so it stays green in CI regardless of the host kernel.
 */
module 
(module) io_uring_send_zc

io_uring — zero-copy send (IORING_OP_SEND_ZC, Linux 6.0).

Ordinary IORING_OP_SEND copies the payload into the socket buffer before the CQE arrives, so the user buffer is free to reuse immediately. Zero-copy send instead pins the user pages and lets the NIC/loopback DMA straight from them — which means the buffer must stay alive until the kernel is done with it, not just until the send is "issued". io_uring signals this with a distinctive two-CQE pattern for a single SEND_ZC SQE:

  1. a transfer-result CQE carrying the byte count, flagged CQEFlags.MORE ("more completions for this user_data are coming"); and

  2. a later notification CQE flagged CQEFlags.NOTIF — emitted once the kernel has released the buffer, so it is now safe to reuse/free.

Passing IORING_SEND_ZC_REPORT_USAGE additionally makes the kernel report, in the notification CQE's res, whether the path was truly zero-copy or it had to fall back to a copy (IORING_NOTIF_USAGE_ZC_COPIED). On loopback the kernel often copies — that is expected and still a successful demonstration of the API and its completion sequence.

This example connects a client socket to a listening server, both on 127.0.0.1, sends a payload from the client with SEND_ZC, asserts the MORE-then-NOTIF CQE sequence, and reads the bytes back on the server end to confirm the transfer.

Companion to the io_uring chronology: see docs/research/async-io/io-uring/timeline.md § "6.0 — Zero-copy send, single-issuer, sync cancel (October 2022)".

Run with: dub run --single send-zc.d

Portability

if io_uring is unavailable (old kernel / sandbox) or the kernel predates SEND_ZC (< 6.0), the program prints a SKIP: line and exits 0 so it stays green in CI regardless of the host kernel.

io_uring_send_zc
;
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) io_uring_send_zc.EINVAL = int core.stdc.errno.EINVAL = 22
EINVAL
,
(alias constant) io_uring_send_zc.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95
EOPNOTSUPP
,
(alias constant) io_uring_send_zc.ENOSYS = int core.stdc.errno.ENOSYS = 38
ENOSYS
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(package) core.sys.posix.arpa
arpa
.
(module) core.sys.posix.arpa.inet

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
inet
:
(alias) io_uring_send_zc.htonl = uint core.sys.posix.arpa.inet.htonl(uint) pure nothrow @nogc @trusted
htonl
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(package) core.sys.posix.netinet
netinet
.
(module) core.sys.posix.netinet.in_

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
in_
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.posix
posix
.
(package) core.sys.posix.sys
sys
.
(module) core.sys.posix.sys.socket

D header file for POSIX.

@copyrightCopyright Sean Kelly 2005 - 2009.@licenseBoost License 1.0.@authorsSean Kelly, Alex Rønne Petersen@standardsThe Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
socket
;
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_send_zc.close = int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
,
(alias) io_uring_send_zc.read = long core.sys.posix.unistd.read(int, void*, ulong) nothrow @nogc
read
;
import
(package) std
std
.
(module) std.range

This module defines the notion of a range. Ranges generalize the concept of arrays, lists, or anything that involves sequential access. This abstraction enables the same set of algorithms (see std.algorithm) to be used with a vast variety of different concrete types. For example, a linear search algorithm such as find works not just for arrays, but for linked-lists, input files, incoming network data, etc.

Guides

There are many articles available that can bolster understanding ranges:

Submodules

This module has two submodules:

The std.range.primitives submodule provides basic range functionality. It defines several templates for testing whether a given object is a range, what kind of range it is, and provides some common range operations.

The std.range.interfaces submodule provides object-based interfaces for working with ranges via runtime polymorphism.

The remainder of this module provides a rich set of range creation and composition templates that let you construct new ranges out of existing ranges:

| chain | Concatenates several ranges into a single range. | | choose | Chooses one of two ranges at runtime based on a boolean condition. | | chooseAmong | Chooses one of several ranges at runtime based on an index. | | chunks | Creates a range that returns fixed-size chunks of the original range. | | cycle | Creates an infinite range that repeats the given forward range indefinitely. Good for implementing circular buffers. | | drop | Creates the range that results from discarding the first n elements from the given range. | | dropBack | Creates the range that results from discarding the last n elements from the given range. | | dropExactly | Creates the range that results from discarding exactly n of the first elements from the given range. | | dropBackExactly | Creates the range that results from discarding exactly n of the last elements from the given range. | | dropOne | Creates the range that results from discarding the first element from the given range. | | dropBackOne | Creates the range that results from discarding the last element from the given range. | | enumerate | Iterates a range with an attached index variable. | | evenChunks | Creates a range that returns a number of chunks of approximately equal length from the original range. | | frontTransversal | Creates a range that iterates over the first elements of the given ranges. | | generate | Creates a range by successive calls to a given function. This allows to create ranges as a single delegate. | | indexed | Creates a range that offers a view of a given range as though its elements were reordered according to a given range of indices. | | iota | Creates a range consisting of numbers between a starting point and ending point, spaced apart by a given interval. | | lockstep | Iterates n ranges in lockstep, for use in a foreach loop. Similar to zip, except that lockstep is designed especially for foreach loops. | | nullSink | An output range that discards the data it receives. | | only | Creates a range that iterates over the given arguments. | | padLeft | Pads a range to a specified length by adding a given element to the front of the range. Is lazy if the range has a known length. | | padRight | Lazily pads a range to a specified length by adding a given element to the back of the range. | | radial | Given a random-access range and a starting point, creates a range that alternately returns the next left and next right element to the starting point. | | recurrence | Creates a forward range whose values are defined by a mathematical recurrence relation. | | refRange | Pass a range by reference. Both the original range and the RefRange will always have the exact same elements. Any operation done on one will affect the other. | | repeat | Creates a range that consists of a single element repeated n times, or an infinite range repeating that element indefinitely. | | retro | Iterates a bidirectional range backwards. | | roundRobin | Given n ranges, creates a new range that return the n first elements of each range, in turn, then the second element of each range, and so on, in a round-robin fashion. | | sequence | Similar to recurrence, except that a random-access range is created. | | slide | Creates a range that returns a fixed-size sliding window over the original range. Unlike chunks, it advances a configurable number of items at a time, not one chunk at a time. | | stride | Iterates a range with stride n. | | tail | Return a range advanced to within n elements of the end of the given range. | | take | Creates a sub-range consisting of only up to the first n elements of the given range. | | takeExactly | Like take, but assumes the given range actually has n elements, and therefore also defines the length property. | | takeNone | Creates a random-access range consisting of zero elements of the given range. | | takeOne | Creates a random-access range consisting of exactly the first element of the given range. | | tee | Creates a range that wraps a given range, forwarding along its elements while also calling a provided function with each element. | | transposed | Transposes a range of ranges. | | transversal | Creates a range that iterates over the n'th elements of the given random-access ranges. | | zip | Given n ranges, creates a range that successively returns a tuple of all the first elements, a tuple of all the second elements, etc. |

Sortedness

Ranges whose elements are sorted afford better efficiency with certain operations. For this, the assumeSorted function can be used to construct a SortedRange from a pre-sorted range. The sort function also conveniently returns a SortedRange. SortedRange objects provide some additional range operations that take advantage of the fact that the range is sorted.

Source

std/range/package.d

@licenseBoost License 1.0.@authorsAndrei Alexandrescu, David Simcha, Jonathan M Davis, and Jack Stouffer. Credit for some of the ideas in building this module goes to Leonardo Maffi.
range
:
(alias template) io_uring_send_zc.iota = std.range.iota(B, E, S)(B begin, E end, S step) if ((isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E))) && isIntegral!S)

Creates a range of values that span the given starting and stopping values.

Example

void main()
{
    import std.stdio;

    // The following groups all produce the same output of:
    // 0 1 2 3 4

    foreach (i; 0 .. 5)
        writef("%s ", i);
    writeln();

    import std.range : iota;
    foreach (i; iota(0, 5))
        writef("%s ", i);
    writeln();

    writefln("%(%s %|%)", iota(0, 5));

    import std.algorithm.iteration : map;
    import std.algorithm.mutation : copy;
    import std.format;
    iota(0, 5).map!(i => format("%s ", i)).copy(stdout.lockingTextWriter());
    writeln();
}
@parambegin The starting value.@paramend The value that serves as the stopping criterion. This value is not included in the range.@paramstep The value to add to the current value at each iteration.@returns

A range that goes through the numbers begin, begin + step, begin + 2 * step, ..., up to and excluding end.

The two-argument overloads have step = 1. If begin < end && step < 0 or begin > end && step > 0 or begin == end, then an empty range is returned. If step == 0 then begin == end is an error.

For built-in types, the range returned is a random access range. For user-defined types that support ++, the range is an input range.

in operator and contains:: iota over an integral/pointer type defines the in operator from the right. val in iota(...) is true when val occurs in the range. When present, it takes step into account - val won't be considered contained if it falls between two consecutive elements of the range. The contains method does the same as in, but from the left-hand side.

iota
;
import
(package) std
std
.
(module) std.algorithm

This package implements generic algorithms oriented towards the processing of sequences. Sequences processed by these functions define range-based interfaces. See also Reference on ranges and tutorial on ranges.

Algorithms are categorized into the following submodules:

Submodule Functions

| Searching | all any balancedParens boyerMooreFinder canFind commonPrefix count countUntil endsWith find findAdjacent findAmong findSkip findSplit findSplitAfter findSplitBefore minCount maxCount minElement maxElement minIndex maxIndex minPos maxPos skipOver startsWith until |

| Comparison | among castSwitch clamp cmp either equal isPermutation isSameLength levenshteinDistance levenshteinDistanceAndPath max min mismatch predSwitch |

| Iteration | cache cacheBidirectional chunkBy cumulativeFold each filter filterBidirectional fold group joiner map mean permutations reduce splitWhen splitter substitute sum uniq |

| Sorting | completeSort isPartitioned isSorted isStrictlyMonotonic ordered strictlyOrdered makeIndex merge multiSort nextEvenPermutation nextPermutation nthPermutation partialSort partition partition3 schwartzSort sort topN topNCopy topNIndex |

| Set operations (setops) | cartesianProduct largestPartialIntersection largestPartialIntersectionWeighted multiwayMerge multiwayUnion setDifference setIntersection setSymmetricDifference |

| Mutation | bringToFront copy fill initializeAll move moveAll moveSome moveEmplace moveEmplaceAll moveEmplaceSome remove reverse strip stripLeft stripRight swap swapRanges uninitializedFill |

Many functions in this package are parameterized with a predicate. The predicate may be any suitable callable type (a function, a delegate, a functor, or a lambda), or a compile-time string. The string may consist of any legal D expression that uses the symbol a (for unary functions) or the symbols a and b (for binary functions). These names will NOT interfere with other homonym symbols in user code because they are evaluated in a different context. The default for all binary comparison predicates is "a == b" for unordered operations and "a < b" for ordered operations.

Example

int[] a = ...;
static bool greater(int a, int b)
{
    return a > b;
}
sort!greater(a);           // predicate as alias
sort!((a, b) => a > b)(a); // predicate as a lambda.
sort!"a > b"(a);           // predicate as string
                           // (no ambiguity with array name)
sort(a);                   // no predicate, "a < b" is implicit

Source

std/algorithm/package.d

@copyrightAndrei Alexandrescu 2008-.@licenseBoost License 1.0.@authorsAndrei Alexandrescu
algorithm
:
(alias template) io_uring_send_zc.copy = std.algorithm.mutation.copy(SourceRange, TargetRange)(SourceRange source, TargetRange target) if (isInputRange!SourceRange && isOutputRange!(TargetRange, ElementType!SourceRange))

Copies the content of source into target and returns the remaining (unfilled) part of target.

Preconditions

target shall have enough room to accommodate the entirety of source.

@paramsource an input range@paramtarget an output range@returnsThe unfilled part of target
copy
,
(alias template) io_uring_send_zc.equal = std.algorithm.comparison.equal(alias pred = "a == b")

Compares two or more ranges for equality, as defined by predicate pred (which is == by default).

equal
,
(alias template) io_uring_send_zc.map = std.algorithm.iteration.map(fun...) if (fun.length >= 1)

Implements the homonym function (also known as transform) present in many languages of functional flavor. The call ``map!(fun)(range) returns a range of which elements are obtained by applying fun(a) left to right for all elements a in range. The original ranges are not changed. Evaluation is done lazily.

@paramfun one or more transformation functions@seeMap (higher-order function)
map
;
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_send_zc.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))

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

writefln
, stderr;
enum
(constant) int io_uring_send_zc.N = 4096
N
= 4096;
int
int D main()
main
()
{ // --- Loopback TCP pair, all via libc (the ring only does the SEND) ------ int
(local variable) int srv
srv
=
int core.sys.posix.sys.socket.socket(int, int, int) nothrow @nogc @safe
socket
(
(enum value) core.sys.posix.sys.socket.AF_INET = 2
AF_INET
,
(enum value) core.sys.posix.sys.socket.SOCK_STREAM = 1
SOCK_STREAM
, 0);
if (
(local variable) int srv
srv
< 0) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("socket(srv) failed"); return 1; }
scope (exit)
int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
(
(local variable) int srv
srv
);
int
(local variable) int one
one
= 1;
int core.sys.posix.sys.socket.setsockopt(int, int, int, scope const(void*), uint) nothrow @nogc
setsockopt
(
(local variable) int srv
srv
,
(enum value) core.sys.posix.sys.socket.SOL_SOCKET = 1
SOL_SOCKET
,
(enum value) core.sys.posix.sys.socket.SO_REUSEADDR = 2
SO_REUSEADDR
, &
(local variable) int one
one
,
(local variable) int one
one
.
(constant) ulong int.sizeof = 4LU
sizeof
);
(struct) core.sys.posix.netinet.in_.sockaddr_in
sockaddr_in
(local variable) core.sys.posix.netinet.in_.sockaddr_in saddr
saddr
;
(local variable) core.sys.posix.netinet.in_.sockaddr_in saddr
saddr
.
(field) ushort core.sys.posix.netinet.in_.sockaddr_in.sin_family
sin_family
=
(enum value) core.sys.posix.sys.socket.AF_INET = 2
AF_INET
;
(local variable) core.sys.posix.netinet.in_.sockaddr_in saddr
saddr
.
(field) ushort core.sys.posix.netinet.in_.sockaddr_in.sin_port
sin_port
= 0; // let the kernel pick a free port
(local variable) core.sys.posix.netinet.in_.sockaddr_in saddr
saddr
.
(field) core.sys.posix.arpa.inet.in_addr core.sys.posix.netinet.in_.sockaddr_in.sin_addr
sin_addr
.
(field) uint core.sys.posix.arpa.inet.in_addr.s_addr
s_addr
=
uint core.sys.posix.arpa.inet.htonl(uint) pure nothrow @nogc @trusted
htonl
(0x7f000001); // 127.0.0.1
if (
int core.sys.posix.sys.socket.bind(int, scope const(core.sys.posix.sys.socket.sockaddr*), uint) nothrow @nogc
bind
(
(local variable) int srv
srv
, cast(
(struct) core.sys.posix.sys.socket.sockaddr
sockaddr
*) &
(local variable) core.sys.posix.netinet.in_.sockaddr_in saddr
saddr
,
(local variable) core.sys.posix.netinet.in_.sockaddr_in saddr
saddr
.
(constant) ulong core.sys.posix.netinet.in_.sockaddr_in.sizeof = 16LU
sizeof
) != 0) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("bind failed"); return 1; }
if (
int core.sys.posix.sys.socket.listen(int, int) nothrow @nogc @safe
listen
(
(local variable) int srv
srv
, 1) != 0) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("listen failed"); return 1; }
// Read back the port the kernel assigned so the client can connect to it.
(struct) core.sys.posix.netinet.in_.sockaddr_in
sockaddr_in
(local variable) core.sys.posix.netinet.in_.sockaddr_in actual
actual
;
(alias) core.sys.posix.sys.socket.socklen_t = uint
socklen_t
(local variable) uint alen
alen
=
(local variable) core.sys.posix.netinet.in_.sockaddr_in actual
actual
.
(constant) ulong core.sys.posix.netinet.in_.sockaddr_in.sizeof = 16LU
sizeof
;
if (
int core.sys.posix.sys.socket.getsockname(int, scope core.sys.posix.sys.socket.sockaddr*, scope uint*) nothrow @nogc
getsockname
(
(local variable) int srv
srv
, cast(
(struct) core.sys.posix.sys.socket.sockaddr
sockaddr
*) &
(local variable) core.sys.posix.netinet.in_.sockaddr_in actual
actual
, &
(local variable) uint alen
alen
) != 0) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("getsockname failed"); return 1; }
int
(local variable) int cli
cli
=
int core.sys.posix.sys.socket.socket(int, int, int) nothrow @nogc @safe
socket
(
(enum value) core.sys.posix.sys.socket.AF_INET = 2
AF_INET
,
(enum value) core.sys.posix.sys.socket.SOCK_STREAM = 1
SOCK_STREAM
, 0);
if (
(local variable) int cli
cli
< 0) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("socket(cli) failed"); return 1; }
scope (exit)
int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
(
(local variable) int cli
cli
);
if (
int core.sys.posix.sys.socket.connect(int, scope const(core.sys.posix.sys.socket.sockaddr*), uint) nothrow @nogc
connect
(
(local variable) int cli
cli
, cast(
(struct) core.sys.posix.sys.socket.sockaddr
sockaddr
*) &
(local variable) core.sys.posix.netinet.in_.sockaddr_in actual
actual
,
(local variable) uint alen
alen
) != 0) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("connect failed"); return 1; }
int
(local variable) int acc
acc
=
int core.sys.posix.sys.socket.accept(int, scope core.sys.posix.sys.socket.sockaddr*, scope uint*) nothrow @nogc
accept
(
(local variable) int srv
srv
, null, null);
if (
(local variable) int acc
acc
< 0) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("accept failed"); return 1; }
scope (exit)
int core.sys.posix.unistd.close(int) nothrow @nogc @trusted
close
(
(local variable) int acc
acc
);
// --- io_uring setup -----------------------------------------------------
(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; } // Probe first: SEND_ZC arrived in 6.0. On older kernels the op is unknown, // so we SKIP cleanly rather than submitting an SQE the kernel can't decode. auto
(local variable) during.Probe probe
probe
=
(local variable) during.Uring io
io
.
during.Probe during.Uring.probe() nothrow @nogc @safe

Probes supported operations

probe
();
if (cast(bool)
(local variable) during.Probe probe
probe
&& !
(local variable) during.Probe probe
probe
.
bool during.Probe.isSupported(during.io_uring.Operation op) const pure nothrow @nogc @safe

Is operation supported?

isSupported
(
(enum) during.io_uring.Operation

Describes the operation to be performed

@seeio_uring_enter(2)
Operation
.
(enum value) during.io_uring.Operation.SEND_ZC = cast(ubyte)47u

IORING_OP_SEND_ZC - zero-copy send

SEND_ZC
))
{
void std.stdio.writefln!char(in char[] fmt) @safe

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

writefln
("SKIP: IORING_OP_SEND_ZC unsupported (kernel < 6.0)");
return 0; } // Deterministic payload: bytes 0,1,2,...,255,0,1,... so we can verify it // round-trips intact on the receiving end. ubyte[
(constant) int io_uring_send_zc.N = 4096
N
]
(local variable) ubyte[4096] payload
payload
;
std.range.iota!(int, int).Result std.range.iota!(int, int)(int begin, int end) pure nothrow @nogc @safe

Creates a range of values that span the given starting and stopping values.

Example

void main()
{
    import std.stdio;

    // The following groups all produce the same output of:
    // 0 1 2 3 4

    foreach (i; 0 .. 5)
        writef("%s ", i);
    writeln();

    import std.range : iota;
    foreach (i; iota(0, 5))
        writef("%s ", i);
    writeln();

    writefln("%(%s %|%)", iota(0, 5));

    import std.algorithm.iteration : map;
    import std.algorithm.mutation : copy;
    import std.format;
    iota(0, 5).map!(i => format("%s ", i)).copy(stdout.lockingTextWriter());
    writeln();
}
@parambegin The starting value.@paramend The value that serves as the stopping criterion. This value is not included in the range.@paramstep The value to add to the current value at each iteration.@returns

A range that goes through the numbers begin, begin` + step`, begin + 2 * step, ..., up to and excluding end.

The two-argument overloads have step = 1. If begin` < `end` && step < 0` or begin > end && step > 0 or begin` == `end, then an empty range is returned. If step == 0 then begin` == `end is an error.

For built-in types, the range returned is a random access range. For user-defined types that support ++, the range is an input range.

in operator and contains:: iota over an integral/pointer type defines the in operator from the right. val in iota(...) is true when val occurs in the range. When present, it takes step into account - val won't be considered contained if it falls between two consecutive elements of the range. The contains method does the same as in, but from the left-hand side.

iota
(0,
(constant) int io_uring_send_zc.N = 4096
N
).
io_uring_send_zc.main.MapResult!(__lambda_L112_C21, Result) io_uring_send_zc.main.map!(std.range.iota!(int, int).Result)(std.range.iota!(int, int).Result r) pure nothrow @nogc @safe

Implements the homonym function (also known as transform) present in many languages of functional flavor. The call ``map!(fun)(range) returns a range of which elements are obtained by applying fun(a) left to right for all elements a in range. The original ranges are not changed. Evaluation is done lazily.

Examples

import std.algorithm.comparison : equal;
import std.range : chain, only;
auto squares =
    chain(only(1, 2, 3, 4), only(5, 6)).map!(a => a * a);
assert(equal(squares, only(1, 4, 9, 16, 25, 36)));

Multiple functions can be passed to map. In that case, the element type of map is a tuple containing one element for each function.

auto sums = [2, 4, 6, 8];
auto products = [1, 4, 9, 16];

size_t i = 0;
foreach (result; [ 1, 2, 3, 4 ].map!("a + a", "a * a"))
{
    assert(result[0] == sums[i]);
    assert(result[1] == products[i]);
    ++i;
}

You may alias map with some function(s) to a symbol and use it separately:

import std.algorithm.comparison : equal;
import std.conv : to;

alias stringize = map!(to!string);
assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ]));
@paramfun one or more transformation functions@seeMap (higher-order function)@paramr an input range@returnsA range with each fun applied to all the elements. If there is more than one fun, the element type will be Tuple containing one element for each fun.
map
!(a => cast(ubyte)(a & 0xff)).
ubyte[] std.algorithm.mutation.copy!(io_uring_send_zc.main.MapResult!(__lambda_L112_C21, Result), ubyte[])(io_uring_send_zc.main.MapResult!(__lambda_L112_C21, Result) source, ubyte[] target) pure nothrow @nogc @safe

Copies the content of source into target and returns the remaining (unfilled) part of target.

Preconditions

target shall have enough room to accommodate the entirety of source.

Examples

int[] a = [ 1, 5 ];
int[] b = [ 9, 8 ];
int[] buf = new int[](a.length + b.length + 10);
auto rem = a.copy(buf);    // copy a into buf
rem = b.copy(rem);         // copy b into remainder of buf
assert(buf[0 .. a.length + b.length] == [1, 5, 9, 8]);
assert(rem.length == 10);   // unused slots in buf

As long as the target range elements support assignment from source range elements, different types of ranges are accepted:

float[] src = [ 1.0f, 5 ];
double[] dest = new double[src.length];
src.copy(dest);

To copy at most n elements from a range, you may want to use take:

import std.range;
int[] src = [ 1, 5, 8, 9, 10 ];
auto dest = new int[](3);
src.take(dest.length).copy(dest);
assert(dest == [ 1, 5, 8 ]);

To copy just those elements from a range that satisfy a predicate, use filter:

import std.algorithm.iteration : filter;
int[] src = [ 1, 5, 8, 9, 10, 1, 2, 0 ];
auto dest = new int[src.length];
auto rem = src
    .filter!(a => (a & 1) == 1)
    .copy(dest);
assert(dest[0 .. $ - rem.length] == [ 1, 5, 9, 1 ]);

retro can be used to achieve behavior similar to STL's copy_backward':

import std.algorithm, std.range;
int[] src = [1, 2, 4];
int[] dest = [0, 0, 0, 0, 0];
src.retro.copy(dest.retro);
assert(dest == [0, 0, 1, 2, 4]);
@paramsource an input range@paramtarget an output range@returnsThe unfilled part of target
copy
(
(local variable) ubyte[4096] payload
payload
[]);
// Enqueue a single zero-copy send. IORING_SEND_ZC_REPORT_USAGE makes the // notification CQE report whether the kernel managed a true zero-copy.
(local variable) during.Uring io
io
.putWith!((ref SubmissionEntry e, int fd, ubyte[] buf) {
e.prepSendZc(fd, buf, MsgFlags.NONE, IORING_SEND_ZC_REPORT_USAGE); e.user_data = 1; })(
during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, int fd, ubyte[] buf) nothrow @nogc @safe { prepSendZc(e, fd, cast(const(ubyte)[])buf, MsgFlags.NONE, 8u); e.user_data = 1LU; } , int, ubyte[])(ref int __param_0, ubyte[] __param_1) 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.
cli
,
during.Uring during.Uring.putWith!(function (ref during.io_uring.SubmissionEntry e, int fd, ubyte[] buf) nothrow @nogc @safe { prepSendZc(e, fd, cast(const(ubyte)[])buf, MsgFlags.NONE, 8u); e.user_data = 1LU; } , int, ubyte[])(ref int __param_0, ubyte[] __param_1) 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.
payload
[]);
// submit(0) just flushes the SQ; we don't ask submit to wait on a count. 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
!= 1) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("submit=%d (expected 1)",
(local variable) const(int) submitted
submitted
); return 1; }
// --- The two-CQE dance -------------------------------------------------- // A single SEND_ZC produces two completions: the transfer result (with // CQEFlags.MORE set) and a separate notification (CQEFlags.NOTIF) once the // buffer is released. Consume exactly two. int
(local variable) int sendRes
sendRes
= int.
(constant) int int.min = -2147483648
min
;
bool
(local variable) bool gotMore
gotMore
,
(local variable) bool gotNotif
gotNotif
,
(local variable) bool zcCopied
zcCopied
;
foreach (
(local variable) int _
_
; 0 .. 2)
{
(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(during.io_uring.CompletionEntry) cqe
cqe
=
(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
;
scope (exit)
(local variable) during.Uring io
io
.
void during.Uring.popFront() pure nothrow @nogc @safe

Move to next CompletionEntry

popFront
();
// Belt-and-suspenders: even if the probe lied, a kernel without SEND_ZC // fails the op itself — treat that as an unsupported-feature SKIP. if (
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
== -
(constant) int core.stdc.errno.EINVAL = 22
EINVAL
||
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
== -
(constant) int core.stdc.errno.EOPNOTSUPP = 95
EOPNOTSUPP
||
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
== -
(constant) int core.stdc.errno.ENOSYS = 38
ENOSYS
)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safe

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

writefln
("SKIP: SEND_ZC rejected by kernel (res=%d) — unsupported (kernel < 6.0)",
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
);
return 0; } if (
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) during.io_uring.CQEFlags during.io_uring.CompletionEntry.flags
flags
&
(enum) during.io_uring.CQEFlags

Flags used with CompletionEntry

CQEFlags
.
(enum value) during.io_uring.CQEFlags.NOTIF = 8u

IORING_CQE_F_NOTIF (from Linux 6.0) Set for notification CQEs. Used to distinguish the per-send completion from the (later) zero-copy notification on IORING_OP_SEND_ZC / IORING_OP_SENDMSG_ZC.

NOTIF
)
{ // Notification CQE: kernel is done with the buffer. With // REPORT_USAGE, res tells us whether it had to fall back to a copy.
(local variable) bool gotNotif
gotNotif
= true;
(local variable) bool zcCopied
zcCopied
= (cast(uint)
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
&
(constant) uint during.io_uring.IORING_NOTIF_USAGE_ZC_COPIED = 2147483648u

Reported in cqe.res of an IORING_CQE_F_NOTIF CQE if IORING_SEND_ZC_REPORT_USAGE was set on the request and the send had to fall back to a copy (at least partially). If unset, the transfer was performed without a copy.

IORING_NOTIF_USAGE_ZC_COPIED
) != 0;
} else { // Transfer-result CQE: byte count + the MORE flag promising a NOTIF. if (
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
< 0) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("send failed: errno %d", -
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
); return 1; }
(local variable) int sendRes
sendRes
=
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) int during.io_uring.CompletionEntry.res

result code for this event

res
;
(local variable) bool gotMore
gotMore
= (
(local variable) const(during.io_uring.CompletionEntry) cqe
cqe
.
(field) during.io_uring.CQEFlags during.io_uring.CompletionEntry.flags
flags
&
(enum) during.io_uring.CQEFlags

Flags used with CompletionEntry

CQEFlags
.
(enum value) during.io_uring.CQEFlags.MORE = 2u

IORING_CQE_F_MORE (from Linux 5.13) If set, parent SQE will generate more CQE entries

MORE
) != 0;
} } if (!
(local variable) bool gotMore
gotMore
) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("transfer CQE lacked CQEFlags.MORE"); return 1; }
if (!
(local variable) bool gotNotif
gotNotif
) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("missing CQEFlags.NOTIF notification CQE"); return 1; }
if (
(local variable) int sendRes
sendRes
!=
(constant) int io_uring_send_zc.N = 4096
N
) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("short send: %d of %d bytes",
(local variable) int sendRes
sendRes
,
(constant) int io_uring_send_zc.N = 4096
N
); return 1; }
// Confirm the bytes actually arrived intact on the server side. ubyte[
(constant) int io_uring_send_zc.N = 4096
N
]
(local variable) ubyte[4096] rx
rx
;
const
(local variable) const(long) rd
rd
=
long core.sys.posix.unistd.read(int, void*, ulong) nothrow @nogc
read
(
(local variable) int acc
acc
, &
(local variable) ubyte[4096] rx
rx
[0],
(local variable) ubyte[4096] rx
rx
.
(constant) ulong ubyte[4096].length = 4096LU
length
);
if (
(local variable) const(long) rd
rd
!=
(constant) int io_uring_send_zc.N = 4096
N
) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("short read: %d of %d bytes", cast(int)
(local variable) const(long) rd
rd
,
(constant) int io_uring_send_zc.N = 4096
N
); return 1; }
if (!
(local variable) ubyte[4096] rx
rx
[].
bool std.algorithm.comparison.equal!().equal!(ubyte[], ubyte[])(ubyte[] __param_0, ubyte[] __param_1) pure nothrow @nogc @safe

Compares two or more ranges for equality, as defined by predicate pred (which is == by default).

Compares two or more ranges for equality. The ranges may have different element types, as long as all are comparable by means of the pred. Performs O(min(rs0.length, rs1.length, ...)) evaluations of pred. However, if equal is invoked with the default predicate, the implementation may take the liberty to use faster implementations that have the theoretical worst-case O(max(rs0.length, rs1.length, ...)).

At least one of the ranges must be finite. If one range involved is infinite, the result is (statically known to be) false.

If the ranges have different kinds of UTF code unit (char, wchar, or dchar), then they are compared using UTF decoding to avoid accidentally integer-promoting units.

Examples

import std.algorithm.comparison : equal;
import std.math.operations : isClose;

int[4] a = [ 1, 2, 4, 3 ];
assert(!equal(a[], a[1..$]));
assert(equal(a[], a[]));
assert(equal!((a, b) => a == b)(a[], a[]));

// different types
double[4] b = [ 1.0, 2, 4, 3];
assert(!equal(a[], b[1..$]));
assert(equal(a[], b[]));

// predicated: ensure that two vectors are approximately equal
double[4] c = [ 1.0000000005, 2, 4, 3];
assert(equal!isClose(b[], c[]));

Tip

equal can itself be used as a predicate to other functions. This can be very useful when the element type of a range is itself a range. In particular, equal can be its own predicate, allowing range of range (of range...) comparisons.

import std.algorithm.comparison : equal;
import std.range : iota, chunks;
assert(equal!(equal!equal)(
    [[[0, 1], [2, 3]], [[4, 5], [6, 7]]],
    iota(0, 8).chunks(2).chunks(2)
));
@paramrs The ranges to be compared.@returnstrue if and only if all ranges compare equal element for element, according to binary predicate pred.
equal
(
(local variable) ubyte[4096] payload
payload
[])) { stderr.
std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @system
writefln
("payload mismatch on receive"); return 1; }
void std.stdio.writefln!(char, int, string)(in char[] fmt, int __param_1, string __param_2) @safe

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

writefln
("ok: SEND_ZC sent %d bytes (transfer CQE F_MORE, then F_NOTIF; path=%s); payload round-tripped",
(local variable) int sendRes
sendRes
,
(local variable) bool zcCopied
zcCopied
? "kernel-copied" : "true zero-copy");
return 0; }