io_uring_multishot(7)
π io_uring_multishot β io_uring multishot requests overview π
okay bestie, gather round, because this is the page where io_uring stops doing things one at a time and starts doing them in bulk like it's restocking a shelf at closing time. this is from the Linux Programmer's Manual, section 7 (the "overviews and conventions" section, iykyk). let's get into it π¦
β¨ NAME β¨
io_uring_multishot β the whole vibe of multishot requests in io_uring, explained.
πΈ DESCRIPTION πΈ
so. multishot requests are a whole class of io_uring operations where one single submission queue entry (SQE) can spit out multiple completion queue entries (CQEs). like, one SQE, many CQEs. it's giving "buy one, get infinity free".
and this is in contrast to normal "oneshot" operations, where each SQE produces exactly one CQE. one in, one out. very demure, very mindful. multishot is the opposite of that, fr fr. π
π Why use multishot requests?
traditional I/O operations require you to submit a new request for each operation. so for high-frequency stuff β like accepting connections or receiving data β you end up with a bunch of overhead, and it's not cute:
- CPU cycles spent preparing and submitting SQEs π
- Memory bandwidth for SQE/CQE processing
- Potential for gaps between completions and new submissions
multishot requests eliminate this overhead by keeping the operation active after each completion. the kernel automatically re-arms the operation, generating a new CQE when the next event occurs. and bonus: the internal poll mechanism stays persistent for the request, so you never have to mess with poll state for each individual operation. no cap, that's the whole glow-up. β¨
multishot operations are most beneficial for:
- Network servers accepting many connections
- Applications receiving data on long-lived connections
- Event monitoring with poll
- Any scenario with repeated identical operations
π¦ How multishot works
when a multishot operation completes, the CQE has the IORING_CQE_F_MORE flag set in cqe->flags. this is the little signal that says "we're still going, bestie, more is coming". π
the operation continues until:
- An error occurs (the final CQE will not have
IORING_CQE_F_MOREset) - The operation is explicitly canceled
- A termination condition specific to the operation is met (e.g., buffer exhaustion for receives)
and the final CQE for a multishot operation will not have IORING_CQE_F_MORE set β that's the flag telling you the operation has terminated. the end, periodt. π¬
π Multishot accept
io_uring_prep_multishot_accept(3) and io_uring_prep_multishot_accept_direct(3) set up a multishot accept operation. each incoming connection generates a CQE with the new file descriptor in cqe->res. like a bouncer who never clocks out. πͺβ¨
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
io_uring_prep_multishot_accept(sqe, listen_fd, NULL, NULL, 0);
the operation keeps accepting connections until an error occurs or it is canceled. and if you use the direct variant with IORING_FILE_INDEX_ALLOC, the accepted sockets get placed directly into the fixed file table. efficient and iconic. π
π Multishot receive
io_uring_prep_recv_multishot(3) sets up a multishot receive operation. every time data arrives on the socket, a CQE is generated. this is typically used with provided buffers (see io_uring_provided_buffers(7)):
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
io_uring_prep_recv_multishot(sqe, sockfd, NULL, 0, 0);
sqe->buf_group = bgid;
sqe->flags |= IOSQE_BUFFER_SELECT;
each completion includes:
IORING_CQE_F_MOREif more completions will followIORING_CQE_F_BUFFERindicating a buffer was selected- The buffer ID in the upper bits of
cqe->flags - The number of bytes received in
cqe->res
the multishot receive terminates when an error occurs, the connection closes, or the buffer ring is exhausted. π
π Multishot recvmsg
io_uring_prep_recvmsg_multishot(3) is similar to multishot receive, but it uses the msghdr structure for scatter/gather I/O and ancillary data. a provided buffer is used for each message, and the kernel writes a struct io_uring_recvmsg_out header at the start of the buffer containing the actual message parameters. fancy, honestly. π§
β Multishot read
io_uring_prep_read_multishot(3) sets up a multishot read operation, typically used with pipes or other stream-oriented file descriptors. like multishot receive, this one is used with provided buffers:
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
io_uring_prep_read_multishot(sqe, fd, 0, 0, bgid);
the operation generates a CQE each time data becomes available to read. that's it, that's the job. π₯
π Multishot poll
io_uring_prep_poll_multishot(3) sets up a multishot poll operation β or you can do it manually by setting the IORING_POLL_ADD_MULTI flag:
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
io_uring_prep_poll_multishot(sqe, fd, POLLIN);
/* or equivalently: */
io_uring_prep_poll_add(sqe, fd, POLLIN);
sqe->len |= IORING_POLL_ADD_MULTI;
each time the polled condition becomes true, a CQE is generated with the triggered events in cqe->res. and unlike oneshot poll, which is automatically removed after triggering, multishot poll stays active. it's committed. π«Ά
heads up bestie: for level-triggered events, the application should be careful to handle the event (e.g., read all available data) before the next poll completion, or spurious wakeups may occur. don't ignore it or you'll be getting ghost notifications. π
π Multishot waitid
io_uring_prep_waitid(3) can operate in multishot mode by setting IORING_ACCEPT_MULTISHOT in the flags. this allows waiting for multiple child process state changes with a single SQE. one SQE, many children, very mother-of-the-group-chat energy. π
π Handling multishot completions
applications must check for IORING_CQE_F_MORE to determine if the operation is still active:
struct io_uring_cqe *cqe;
while (io_uring_peek_cqe(ring, &cqe) == 0) {
if (cqe->res < 0) {
/* Error occurred, operation terminated */
handle_error(cqe->res);
} else {
process_completion(cqe);
}
if (!(cqe->flags & IORING_CQE_F_MORE)) {
/* Operation terminated, may need to resubmit */
rearm_if_needed();
}
io_uring_cqe_seen(ring, cqe);
}
π¦ Canceling multishot operations
multishot operations can be canceled using io_uring_prep_cancel(3) or related functions. the cancellation request generates its own CQE, and the multishot operation generates a final CQE (typically with -ECANCELED) without IORING_CQE_F_MORE set. two CQEs, one breakup. π
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
io_uring_prep_cancel64(sqe, user_data, 0);
πΈ Integration with provided buffers
multishot receive and read operations are designed to work with provided buffer rings (see io_uring_provided_buffers(7)). each completion consumes a buffer from the ring, and the application must return buffers to the ring to keep the operation running. it's a whole cycle, bestie, you gotta feed it. π
if the buffer ring becomes empty, the multishot operation terminates with -ENOBUFS. so applications should ensure adequate buffers are available and promptly return used buffers to the ring. don't starve it!! π
β NOTES β
- Always check
IORING_CQE_F_MOREto know if a multishot operation is still active. - Multishot operations may generate many CQEs quickly. Ensure the CQ ring is large enough to avoid overflow.
- When using provided buffers with multishot receives, monitor buffer availability to prevent premature termination.
- Multishot operations are edge-triggered conceptually β they generate completions when events occur, not continuously while conditions are true.
- Error completions from multishot operations do not have
IORING_CQE_F_MOREset, indicating termination.
π SEE ALSO π
io_uring(7)io_uring_provided_buffers(7)io_uring_prep_multishot_accept(3)io_uring_prep_recv_multishot(3)io_uring_prep_recvmsg_multishot(3)io_uring_prep_read_multishot(3)io_uring_prep_poll_add(3)io_uring_prep_poll_multishot(3)io_uring_prep_cancel(3)
and that's the whole page, bestie!! this one's from Linux, dated January 18, 2025, and it's page io_uring_multishot(7) β go forth and multishot 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