io_uring_l...d_requests(7)
πΈ io_uring_l...d_requests β the manual page, bestie edition πΈ
okay so this is the io_uring_l...d_requests(7) page from the Linux Programmer's Manual. it's giving "chain ur async ops together and let the kernel handle the drama." sit down, grab a strawberry π, and let's get into it.
β¨ NAME β¨
io_uring_linked_requestsβ the whole overview of io_uring linked requests π
π¦ DESCRIPTION π¦
Linked requests let applications chain multiple io_uring operations together, creating dependencies between them. when requests are linked, they run sequentially rather than concurrently β each request starts only after the previous one in the chain completes. no cutting the line, no chaos. periodt.
π Why use linked requests? π
Normal io_uring submissions are independent and may execute in any order, or concurrently. but bestie, some operations have natural dependencies:
- Read from one file, then write to another
- Connect to a server, then send a request
- Accept a connection, then receive data
- Perform an operation with a timeout
without linking, applications would have to sit there waiting for completions and manually submit follow-up requests. linked requests let you submit the entire chain at once, which cuts down the round trips between user space and the kernel. less back-and-forth, more slay. β¨
Linked requests are most beneficial when:
- Operations must execute in a specific order
- Later operations depend on earlier ones succeeding
- You want to attach a timeout to an operation
- Reducing submission latency is important
π Creating linked requests π
you link requests by setting the IOSQE_IO_LINK flag on a request. that links it to the next request in the submission. the chain keeps going until a request without the link flag shows up. iconic behavior honestly.
struct io_uring_sqe *sqe;
/* First request in chain */
sqe = io_uring_get_sqe(ring);
io_uring_prep_read(sqe, fd_in, buf, len, 0);
sqe->flags |= IOSQE_IO_LINK;
/* Second request, linked to first */
sqe = io_uring_get_sqe(ring);
io_uring_prep_write(sqe, fd_out, buf, len, 0);
sqe->flags |= IOSQE_IO_LINK;
/* Third request, end of chain (no link flag) */
sqe = io_uring_get_sqe(ring);
io_uring_prep_fsync(sqe, fd_out, 0);
io_uring_submit(ring);
so in this example: the read completes first, then the write, then the fsync. each operation waits for the previous one to complete before it even starts. it's giving very orderly, very demure. π§
π Soft links vs hard links π
there are two types of links, and the difference is all about how they handle errors. this is the part where you pay attention bestie.
π Soft links (IOSQE_IO_LINK) π
if a request in the chain fails β like, returns a negative error code β then all subsequent requests in the chain are canceled with -ECANCELED. super useful when later operations depend on earlier ones succeeding. if the vibe breaks, everybody after it gets dropped.
π Hard links (IOSQE_IO_HARDLINK) π
the chain keeps executing even if a request fails. each request runs regardless of the outcome of previous requests. this is the one for when you want to attempt all the operations even if some of them flop. resilience, mama.
/* Soft link: write is canceled if read fails */
sqe = io_uring_get_sqe(ring);
io_uring_prep_read(sqe, fd, buf, len, 0);
sqe->flags |= IOSQE_IO_LINK;
sqe = io_uring_get_sqe(ring);
io_uring_prep_write(sqe, fd2, buf, len, 0);
/* Hard link: write runs even if read fails */
sqe = io_uring_get_sqe(ring);
io_uring_prep_read(sqe, fd, buf, len, 0);
sqe->flags |= IOSQE_IO_HARDLINK;
sqe = io_uring_get_sqe(ring);
io_uring_prep_write(sqe, fd2, buf, len, 0);
β° Link timeouts β°
a super common use of linked requests is adding a timeout to an operation. the IORING_OP_LINK_TIMEOUT operation β which you set up with io_uring_prep_link_timeout(3) β was literally designed for exactly this:
struct __kernel_timespec ts = { .tv_sec = 5, .tv_nsec = 0 };
/* The operation to be timed */
sqe = io_uring_get_sqe(ring);
io_uring_prep_read(sqe, fd, buf, len, 0);
sqe->flags |= IOSQE_IO_LINK;
/* The timeout, linked to the read */
sqe = io_uring_get_sqe(ring);
io_uring_prep_link_timeout(sqe, &ts, 0);
io_uring_submit(ring);
now here's the fork in the road, bestie. if the read completes before the timeout:
- The read CQE has the actual result
- The timeout CQE has
-ECANCELED
and if the timeout expires first:
- The read CQE has
-ECANCELED(or-EINTRif it was in progress) - The timeout CQE has
-ETIME
β οΈ link timeouts only apply to the immediately preceding request in the chain. if you wanna timeout an entire chain, the timeout has to be linked after the last operation. don't forget this one, it's lowkey a trap.
π«Ά Completion ordering π«Ά
each request in a linked chain generates its own CQE. and the completions for linked requests are ordered β the CQE for an earlier request in the chain gets posted before the CQE for a later request. no cap.
applications can rely on this ordering when processing completions. however β if other unlinked requests are in flight, their completions may be interleaved with the chain's completions. so don't be shocked when other stuff shows up in the mix. π
π Error handling π
for soft-linked chains, error handling is honestly straightforward:
- Check each CQE's result
- If a request failed, all subsequent requests will have
-ECANCELED - The first non-canceled error indicates where the chain broke
/* Processing a linked chain's completions */
for (int i = 0; i < chain_length; i++) {
io_uring_wait_cqe(ring, &cqe);
if (cqe->res == -ECANCELED) {
/* Previous request in chain failed */
} else if (cqe->res < 0) {
/* This request failed, caused chain break */
handle_error(cqe->res);
} else {
/* Success */
handle_success(cqe->res);
}
io_uring_cqe_seen(ring, cqe);
}
π Common patterns π
these are the classic combos, iykyk.
π Copy with sync
read data, write it elsewhere, then sync:
io_uring_prep_read(sqe1, src_fd, buf, len, 0);
sqe1->flags |= IOSQE_IO_LINK;
io_uring_prep_write(sqe2, dst_fd, buf, len, 0);
sqe2->flags |= IOSQE_IO_LINK;
io_uring_prep_fsync(sqe3, dst_fd, 0);
β³ Connect with timeout
attempt a connection with a time limit:
io_uring_prep_connect(sqe1, sockfd, addr, addrlen);
sqe1->flags |= IOSQE_IO_LINK;
io_uring_prep_link_timeout(sqe2, &timeout, 0);
π¨ Send after connect
connect, then immediately send data:
io_uring_prep_connect(sqe1, sockfd, addr, addrlen);
sqe1->flags |= IOSQE_IO_LINK;
io_uring_prep_send(sqe2, sockfd, data, len, 0);
β NOTES β
- Linked requests must be submitted together in the same
io_uring_submit(3)call. the chain is defined by the order of SQEs in the submission. - The link flag on the last request in a chain is ignored β it has nothing to link to. it's just out there linking to the void. π
- Chains can be arbitrarily long, limited only by SQ ring size.
- Mixing
IOSQE_IO_LINKandIOSQE_IO_HARDLINKin the same chain is allowed. each link's type determines what happens if that specific request fails. - Linked requests share the same personality if set, allowing credential inheritance through the chain.
- If a request in a chain is canceled β e.g., via
io_uring_prep_cancel(3)β the chain breaks as if that request had failed. - Linked requests have performance implications: they force sequential execution, which prevents the kernel from optimizing or parallelizing operations. use links only when ordering is required. for independent operations, submitting them without links lets the kernel execute them concurrently or reorder them for better performance. don't link for the aesthetic, link with intention. π
π¦ SEE ALSO π¦
and that's the whole page, bestie!! π this one came from Linux, dated January 18, 2025, and it lives in the manual as io_uring_l...d_requests(7). go forth and chain responsibly. mwah ππ«Ά
π¦ where this page comes from
- alpine 3.24: liburing-doc 2.14-r0 Β· LGPL-2.1-or-later OR MIT
- arch rolling: liburing 2.15-1 Β· GPL-2.0-only WITH Linux-syscall-note OR MIT AND LGPL-2.0-or-later AND MIT
- nixpkgs unstable: liburing 2.14
- ubuntu resolute: liburing-dev 2.14-1
- ubuntu stonking: liburing-dev 2.15-1
- π the page's license: LGPL-2.0-or-later
- Copyright (C) 2025 Jens Axboe <axboe@kernel.dk>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms