An io_uring submission queue can contain many operations at once, but not every operation has to be independent. Setting IOSQE_IO_LINK on a submission queue entry binds it to the next entry, forming a chain in which execution order and failure propagation become part of the kernel-visible request structure.
That changes the contract compared with submitting two unrelated SQEs and coordinating them after completion. A linked chain expresses dependency before the kernel starts processing the operations. The distinction matters when a later request is valid only after an earlier request has completed, or when failure of one stage should prevent the remaining stages from running.
The link flag belongs to the preceding operation
IOSQE_IO_LINK does not mark an SQE as a dependent child. It states that the current SQE is linked to the SQE that follows it.
A three-operation chain therefore has the flag on the first two entries:
SQE 0: operation A + IOSQE_IO_LINK
SQE 1: operation B + IOSQE_IO_LINK
SQE 2: operation CThe absence of the flag on the final entry terminates the chain. Another SQE submitted after C is independent unless a new link relation is established.
This representation makes chain boundaries a property of submission metadata rather than completion-side application state. Code that constructs SQEs must preserve both their order and their flags. Reordering entries that look independent at the call site can silently change the dependency graph.
Completion gates the next request
For a normal linked chain, the next request is not issued until the previous request completes successfully according to io_uring link semantics. This is stronger than submission order alone.
Submission order does not generally mean completion order for unrelated asynchronous requests. The kernel can have multiple requests in flight, and their completions can arrive in an order determined by the operations and underlying subsystems. A link adds an explicit sequencing constraint between adjacent members of the chain.
Consider a file operation followed by a timeout intended to participate in one compound action. If both are submitted independently, both can become active without a dependency between them. Linking them changes their relationship: the second member is governed by the completion state of the first and the chain rules.
The application still receives completion queue entries for operations. Linking does not turn several SQEs into one CQE or erase per-request results. It changes request scheduling and cancellation relationships while preserving individual completion reporting.
Failure can terminate the remaining chain
A normal link carries failure propagation. If a linked request fails, subsequent requests in that chain are canceled rather than issued as though the preceding stage had succeeded. Canceled members normally report -ECANCELED in their completion result.
The break condition is broader than a negative errno-style result. For IOSQE_IO_LINK, io_uring can treat an unexpected result as an error for chaining; a short read is a documented example that can terminate the remaining chain. Code must therefore account for the opcode-specific result semantics rather than reducing the rule to cqe->res < 0.
Linked requests remove a class of completion-side orchestration, but they do not replace domain validation. The kernel can enforce its request-result rules; it cannot infer whether a result satisfies an application invariant beyond those rules.
The cancellation boundary is also local to the chain. Independent SQEs in the same submission batch are not canceled merely because a linked chain beside them fails.
Hard links change failure propagation
IOSQE_IO_HARDLINK retains ordering between adjacent requests but changes what happens after failure. A failure in one member does not automatically cancel the rest of the chain.
That distinction separates two contracts:
IOSQE_IO_LINK
A fails -> later linked members are canceled
IOSQE_IO_HARDLINK
A fails -> chain ordering remains, later members can continueA hard link is appropriate only when later operations remain valid after an earlier failure. Cleanup is a common shape for this requirement: an application may need a later action to run even if the preceding operation returns an error.
Using a hard link merely to avoid cancellation weakens the dependency contract. The later operation must be safe with every result the earlier member can produce, including errors. Otherwise the chain preserves order while allowing an invalid state transition.
Chains are not transactions
A linked chain can resemble a small transaction because it groups ordered work and can suppress later operations after failure. It does not provide transactional rollback.
If operation A changes external state and operation B then fails, linking does not undo A. The first operation has already completed. Filesystem mutations, bytes sent to a socket, or other externally visible effects remain governed by the semantics of those operations.
This produces an asymmetric failure model:
A succeeds -> effect A may be visible
B fails -> C can be canceled
effect A is not rolled backAtomicity must come from the underlying API if it is required. A chain can control issue order and failure propagation, but it cannot combine unrelated kernel operations into an all-or-nothing commit.
That boundary is especially important for persistent state. Ordering two writes through a link does not by itself create crash consistency, durable commit, or atomic replacement. Those properties depend on filesystem and storage semantics plus the synchronization operations used by the application.
A chain also creates a resource-lifetime dependency
An SQE can refer to memory, file descriptors, fixed resources, socket addresses, or operation-specific structures. Linking requests does not relax the lifetime requirements of those objects.
If a later linked request refers to a buffer, that buffer must remain valid for the period required by the relevant io_uring operation contract. The fact that the request is waiting behind another member does not make early reuse safe. Its execution is deferred, not discarded.
The same issue appears with file descriptors when ordinary descriptors are used. Application code must account for descriptor lifetime and reuse according to the API’s rules. Registered resources can change the mechanics, but they introduce their own registration and update contracts.
A useful design consequence follows: the chain encodes kernel-side operation dependency, while ownership of user-space resources remains an application responsibility.
Completion handling still needs request identity
Because each member produces its own completion, applications still need to associate CQEs with logical operations. The user_data field remains the normal mechanism for carrying application-defined identity through submission and completion.
A chain does not guarantee that completion handling can ignore individual results. Each CQE can carry information needed for resource release, error classification, accounting, or protocol state.
This also prevents a common abstraction mistake: representing an entire chain with only one application state record while discarding member identity. That can work for a tightly constrained wrapper, but the wrapper must still consume every CQE and correctly map cancellation and partial progress to its own state machine.
Dependency belongs where its failure semantics are explicit
Linked SQEs are most precise when the dependency is already known at submission time and the desired failure behavior matches the kernel contract. They can reduce races created by submitting a second operation only after user space observes the first CQE, because the dependency is present before either stage completes.
They are less suitable when the next operation depends on inspecting application-level data from the previous result. In that case, user space must observe and classify the result before constructing the next action. Encoding a chain in advance would commit to a transition before the application has the information required to choose it.
The boundary is not simply synchronous versus asynchronous control. It is the location of the decision. IOSQE_IO_LINK moves a predetermined sequencing and cancellation relation into the submitted request graph. Decisions that depend on higher-level result interpretation remain outside that graph.