Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spec layer 0 — Module io_uring, Stage 3d: multishot operations

Technical specification — Version 1.0 (target kernel: Linux 6.12 LTS)

Position. Stage 3d specifies multishot operations: a single submission produces multiple completions. Sub-module air-sys-syscall::io_uring::multishot. Relies on the completion flag CQE_F_MORE (axis E), on the provided buffers from Stage 3b (for recv/read), and on the direct descriptors from Stage 3a (for accept). Reuses the core from Stage 1. No registered opcode, one dedicated opcode (READ_MULTISHOT, 49) and op flags (ACCEPT_MULTISHOT, RECV_MULTISHOT, POLL_ADD_MULTI, TIMEOUT_MULTISHOT).


1. Principle and lifecycle

A one-shot operation submits one SQE, receives one CQE, and releases its slot. A multishot operation submits one SQE and receives a stream of CQEs:

  • each intermediate completion carries CQE_F_MORE: “more completions will follow for this same SQE”;
  • the final (terminal) completion does not have CQE_F_MORE: the multishot is done (error, buffer starvation, or cancellation).

Interaction with slab S1. The slot stays alive as long as completions carry CQE_F_MORE; it is released only at the completion without F_MORE. This is the second case (after the zero-copy NOTIF from Stage 2b) where a slot outlives its first completion — the generation of the SubmissionToken guards against late CQEs after cancellation (Stage 1 §4.2).

Token. A multishot operation returns a MultishotToken (distinct from the one-shot SubmissionToken). All its completions carry it:

#![allow(unused)]
fn main() {
impl Completion {
    /// Multishot token if this completion comes from a multishot.
    pub fn multishot_token(&self) -> Option<MultishotToken>;
    // has_more() (Stage 1) = CQE_F_MORE : true as long as the stream continues.
}
}

Re-arming. When a multishot ends (has_more() == false), the application resubmits if it wants to continue. The facade makes termination explicit; no hidden re-arming.


2. Accept multishot

#![allow(unused)]
fn main() {
impl IoUring {
    pub fn submit_accept_multishot(&mut self, listener: BorrowedFd<'_>, flags: AcceptFlags)
        -> Result<MultishotToken, Errno>;
    /// Direct-descriptor variant: each connection lands in an auto-allocated slot
    /// in the FixedFdTable (Stage 3a).
    pub fn submit_accept_multishot_direct(&mut self, listener: BorrowedFd<'_>, flags: AcceptFlags)
        -> Result<MultishotToken, Errno>;
}
}
  • Op: ACCEPT (13) + IORING_ACCEPT_MULTISHOT. A single SQE accepts continuously: each incoming connection produces a completion carrying the accepted FD (accepted_fd()), CQE_F_MORE maintained.
  • Direct variant: each connection goes into a direct slot (auto-alloc, FixedSlotTarget::Alloc) — ideal for a very high connection-rate server (compositor, AirCom): no ordinary FD, no userspace table to manage. Links to Stage 3a.
  • Termination: on error (e.g. listener closed) ⇒ completion without F_MORE. Use case: a server makes one submission at startup and consumes connections as they arrive.

3. Recv / Read multishot (with provided buffers)

#![allow(unused)]
fn main() {
impl IoUring {
    pub fn submit_receive_multishot(&mut self, sock: BorrowedFd<'_>, group: &ProvidedBufferRing, flags: MessageFlags)
        -> Result<MultishotToken, Errno>;
    pub fn submit_read_multishot(&mut self, fd: BorrowedFd<'_>, group: &ProvidedBufferRing, offset: Option<u64>)
        -> Result<MultishotToken, Errno>;
}
}
  • Ops: RECV (27) + IORING_RECV_MULTISHOT; READ_MULTISHOT (49, dedicated opcode). Require provided buffers (Stage 3b, IOSQE_BUFFER_SELECT): each data arrival takes a buffer from the group and produces a completion.
  • Completion: into_provided_buffer(group) returns the chosen buffer (id + bytes), a RAII guard that replenishes on drop (Stage 3b §4). Incremental consumption (CQE_F_BUF_MORE, Stage 3b §5) composes with multishot.
  • Buffer starvation: if the group is empty when data arrives, the completion carries -ENOBUFS and terminates the multishot (no F_MORE). The application replenishes the group then resubmits. The facade signals this case distinctly (termination due to starvation vs. network error).
  • Major benefit for AirCom / servers: a single SQE per connection serves its entire incoming stream, without a buffer pre-committed per idle connection.

4. Poll multishot

#![allow(unused)]
fn main() {
impl IoUring {
    pub fn submit_poll_multishot(&mut self, fd: BorrowedFd<'_>, events: PollEvents)
        -> Result<MultishotToken, Errno>;
}
}
  • Op: POLL_ADD (6) + IORING_POLL_ADD_MULTI. Each state transition of the FD toward the monitored events produces a completion (into_poll_result() -> PollEvents), F_MORE maintained.
  • Level-triggered: IORING_POLL_ADD_LEVEL exposed via an option (PollEvents + level). Otherwise, edge-triggered.
  • Cancellation: submit_poll_remove/cancel_multishot.

5. Timeout multishot

#![allow(unused)]
fn main() {
impl IoUring {
    pub fn submit_timeout_multishot(&mut self, interval: Duration, flags: TimeoutFlags)
        -> Result<MultishotToken, Errno>;
}
}
  • Op: TIMEOUT (11) + IORING_TIMEOUT_MULTISHOT. Emits a completion at a regular interval (repeating timer) until cancelled — useful for a periodic heartbeat in the reactor without resubmitting on every tick.
  • Termination: cancel_multishot or error.

6. Cancellation

#![allow(unused)]
fn main() {
impl IoUring {
    pub fn cancel_multishot(&mut self, token: MultishotToken) -> Result<(), Errno>;
}
}
  • Cancels an in-flight multishot (via ASYNC_CANCEL, Stage 2c, targeting the token). The terminal completion (without F_MORE, often -ECANCELED) releases the slot; any late CQEs are filtered by generation (S1).
  • submit_cancel(CancelTarget::…) (Stage 2c) remains usable for grouped cancellations (by FD, by op, Any).

7. Summary

Op / flagFacadeCompletions
ACCEPT + ACCEPT_MULTISHOTsubmit_accept_multishot[_direct]stream of accepted FDs
RECV + RECV_MULTISHOTsubmit_receive_multishotstream of provided buffers
READ_MULTISHOT (49)submit_read_multishotstream of provided buffers
POLL_ADD + POLL_ADD_MULTIsubmit_poll_multishotstream of events
TIMEOUT + TIMEOUT_MULTISHOTsubmit_timeout_multishotstream of ticks
ASYNC_CANCELcancel_multishottermination

All share: MultishotToken, the CQE_F_MORE invariant, slot release at the terminal completion.


8. Types added / shared

New: MultishotToken (already declared in Stage 1, semantics fixed here). Reuses: AcceptFlags, MessageFlags, PollEvents, TimeoutFlags, ProvidedBufferRing/ProvidedBuffer (Stage 3b), FixedSlotTarget (Stage 3a). Completion method: multishot_token.


9. Test strategy

  • Integration: accept_multishot on a listener, N connections ⇒ N completions with F_MORE, listener closed ⇒ terminal completion; direct variant (FD in slots); recv_multishot + buffer group, stream of datagrams, starvation ⇒ -ENOBUFS terminating the multishot, replenish + resubmit; poll_multishot on a pipe written multiple times; timeout_multishot (N ticks then cancel); cancel_multishot (termination, late CQE filtered).
  • S1 lifecycle: slot remains occupied while F_MORE, released at terminal completion; generation filters post-cancellation CQEs (property-based).
  • Safety: Miri on multishot slot restitution; no double-free; provided buffers correctly returned in multishot.
  • Coverage: 100% lines + branches.

10. Key decisions that emerged at Stage 3d

  1. Distinct MultishotToken — a stream of completions, not a one-shot op; the type prevents confusing the two lifecycles.
  2. Slot alive until the terminal completion (!F_MORE) — an assumed extension of S1 (like zero-copy NOTIF); generation guards against late CQEs.
  3. Explicit termination, explicit re-arming — no hidden resubmission (Principle 7); -ENOBUFS signalled distinctly from network error.
  4. accept_multishot_direct preferred for high connection-rate servers — connections as direct descriptors (links Stage 3a), relevant for AirCom/compositor.
  5. recv/read multishot backed by provided buffers — no buffer pre-committed per connection (links Stage 3b).

11. Next steps

Next spec: io-uring-3e-shared-en.md (multi-thread usage: LockedIoUring, RingPool thread-per-core, SqpollIoUring).


Document license: MPL 2.0 Status: Technical specification for Stage 3d (multishot) of the air-sys-syscall::io_uring module, target kernel 6.12 LTS.