Spec couche 0 — Module io_uring, Temps 3c : opérations liées (linked)
Spécification technique — Version 1.0 (cible kernel : Linux 6.12 LTS)
Position. Le Temps 3c spécifie les chaînes d’opérations liées : exprimer qu’une opération ne démarre qu’après le succès (ou l’achèvement) de la précédente, sans aller-retour userspace entre les deux. Sous-module
air-sys-syscall::io_uring::linked. Repose sur les drapeaux SQEIOSQE_IO_LINK/IOSQE_IO_HARDLINK(axe D) et intègre l’opLINK_TIMEOUT(15, Temps 2c). Réutilise le cœur du Temps 1. Aucun register opcode.
⚠️ Correction de
io-uring-overview.md. L’ancien overview décrit la sémantique soft/hard link à l’envers. La sémantique correcte (vérifiée contre le man pageio_uring_enter(2)) est fixée en §2 ci-dessous. L’overview sera corrigé lors de sa réconciliation (son rôle d’inventaire est déjà repris par le doc maître).
1. Principe
Plusieurs SQE soumis ensemble peuvent former une chaîne : chaque maillon ne démarre qu’après l’achèvement du précédent. Le marquage se fait par un drapeau sur le maillon prédécesseur :
IOSQE_IO_LINKsur l’op A ⇒ l’op B (SQE suivant) est liée à A.- La queue de chaîne est le premier SQE sans drapeau de lien.
Sans chaîne, des SQE soumis ensemble peuvent s’exécuter dans un ordre
arbitraire et en parallèle. La chaîne impose l’ordre et la dépendance —
indispensable pour, p. ex., socket → connect → send ou openat2 → read → close en une seule soumission.
2. Sémantique soft vs hard (point critique)
Les deux types de lien diffèrent par leur propagation des erreurs, pas par l’ordre (l’ordre est garanti dans les deux cas) :
2.1 Soft link — IOSQE_IO_LINK
Si un maillon se termine en erreur, la chaîne est rompue : tous les maillons restants non démarrés sont annulés et complétés avec
-ECANCELED.
Définition large de l’« erreur » : io_uring considère tout résultat
inattendu comme une erreur rompant la chaîne — y compris un read/recv
court (moins d’octets que demandé). C’est le piège classique : une chaîne
read(N) → process est rompue si le read rend moins de N octets. À documenter
fortement dans la façade.
2.2 Hard link — IOSQE_IO_HARDLINK
Un hard link ne rompt pas la chaîne sur un résultat d’erreur de complétion : les maillons suivants s’exécutent quand même.
Nuances :
- Le hard link implique le soft link (l’ordre reste garanti).
- Il ne protège que des erreurs de complétion : si la soumission du parent échoue, la chaîne casse quand même.
2.3 Tableau
| Ordre garanti | Erreur de complétion d’un maillon | Échec de soumission du parent | |
|---|---|---|---|
soft (IO_LINK) | oui | rompt la chaîne (-ECANCELED sur les suivants) ; short read = erreur | rompt |
hard (IO_HARDLINK) | oui | n’interrompt pas (suivants exécutés) | rompt |
3. API : LinkedChainBuilder
#![allow(unused)]
fn main() {
impl IoUring {
/// Démarre la construction d'une chaîne liée.
pub fn link_chain(&mut self) -> LinkedChainBuilder<'_>;
}
pub struct LinkedChainBuilder<'ring> { /* &mut IoUring en mode staging */ }
impl<'ring> LinkedChainBuilder<'ring> {
/// Premier maillon. `op` appelle un `submit_*` normal, mis en attente
/// (staging) au lieu d'être publié.
pub fn first<F>(self, op: F) -> Result<Self, Errno>
where F: FnOnce(&mut IoUring) -> Result<SubmissionToken, Errno>;
/// Maillon **soft-lié** au précédent (pose `IO_LINK` sur le précédent).
pub fn then<F>(self, op: F) -> Result<Self, Errno>
where F: FnOnce(&mut IoUring) -> Result<SubmissionToken, Errno>;
/// Maillon **hard-lié** au précédent (pose `IO_HARDLINK` sur le précédent).
pub fn then_hard<F>(self, op: F) -> Result<Self, Errno>
where F: FnOnce(&mut IoUring) -> Result<SubmissionToken, Errno>;
/// Borne dans le temps le **maillon précédent** via un `LINK_TIMEOUT`
/// (op 15). Doit suivre immédiatement l'op à borner.
pub fn with_link_timeout(self, spec: TimeoutSpec, flags: TimeoutFlags)
-> Result<Self, Errno>;
/// Publie la chaîne **entière, atomiquement**. Rend les jetons dans l'ordre.
pub fn submit(self) -> Result<ChainTokens, Errno>;
}
/// Jetons des maillons d'une chaîne, dans l'ordre de soumission.
pub struct ChainTokens { /* Vec<SubmissionToken> */ }
}
3.1 Mode staging (réutilise les 55 submit_* sans duplication)
Pendant la durée du builder, le &mut IoUring est en mode staging : un
submit_* appelé dans la closure réserve un slot S1 et écrit un SQE (avec le
drapeau de lien adéquat sur le prédécesseur), sans publier la queue. La
publication n’a lieu qu’au submit() final, qui avance la queue SQ une seule
fois (§3.3). On évite ainsi de dupliquer 55 méthodes prepare_* : la même
surface submit_* sert en mode direct et en mode chaîne (cohérent avec
Principe 7 — l’usage est explicitement délimité par link_chain()).
3.2 Réservation atomique des slots
submit() ne publie que si tous les slots de la chaîne ont pu être réservés
(S1). Si le slab ne peut pas contenir toute la chaîne, le builder retourne
Err(EBUSY) avant d’écrire quoi que ce soit — pas de chaîne partielle.
3.3 Publication
Tous les SQE de la chaîne sont écrits puis la queue SQ est avancée par un seul store release (§3.2 du Temps 1) → le kernel voit la chaîne complète d’un coup, garantissant le respect des liens.
3.4 Exemple
#![allow(unused)]
fn main() {
// socket → connect → send, en une soumission liée.
let tokens = ring.link_chain()
.first(|r| r.submit_socket(SocketDomain::Inet, SocketType::Stream, 0))?
.then(|r| r.submit_connect(sock, addr))? // démarre après le socket
.then(|r| r.submit_send(sock, buf, MessageFlags::empty()))?
.submit()?;
}
4. Complétions d’une chaîne
- Une complétion par maillon, dans l’ordre. Chaque maillon a son slot S1 et restitue son buffer normalement.
- Maillon annulé (soft link rompu) : complétion avec
res == -ECANCELED; slot/buffer restitués. - Avec
IOSQE_CQE_SKIP_SUCCESS(Temps 1,SubmitOptions) sur les maillons intermédiaires, on peut n’attendre que la complétion finale (les succès intermédiaires ne postent pas de CQE) — utile pour une chaîne dont seul le résultat final importe. ChainTokenspermet de corréler chaque complétion à son maillon.
5. link_timeout (intégration de l’op 15)
with_link_timeout(spec, flags) insère un LINK_TIMEOUT qui borne le maillon
précédent :
- si le timeout expire avant la fin du maillon ⇒ le maillon est annulé
(
-ECANCELED) et la chaîne est rompue (le timeout, lui, complète) ; - si le maillon finit avant ⇒ le timeout est annulé automatiquement.
C’est l’unique manière sûre d’émettre un LINK_TIMEOUT dans Air (l’émettre en
op isolée est refusé, Temps 2c §3.3). Exemple : borner un connect à 2 s.
#![allow(unused)]
fn main() {
let tokens = ring.link_chain()
.first(|r| r.submit_connect(sock, addr))?
.with_link_timeout(TimeoutSpec::after(Duration::from_secs(2)), TimeoutFlags::empty())?
.submit()?;
}
6. Types ajoutés / partagés
Nouveaux : LinkedChainBuilder<'ring>, ChainTokens. Réutilise :
SubmissionToken, SubmitOptions (drain, skip-cqe), TimeoutSpec,
TimeoutFlags (Temps 2c). Aucun nouveau type kernel.
7. Stratégie de tests
- Intégration : chaîne
socket→connect→sendréussie (ordre observé) ; chaîne soft avec erreur au milieu ⇒ maillons suivants-ECANCELED; short read rompant une chaîne soft (test explicite du piège) ; chaîne hard où une erreur n’interrompt pas ;link_timeoutexpirant (maillon annulé) et non expirant (timeout annulé) ;skip_cqe_on_successsur intermédiaires (seule la complétion finale arrive). - Property-based : pour toute chaîne, ordre des complétions = ordre de soumission ; nombre de CQE cohérent avec les skip ; aucun slot/buffer perdu même en cas de rupture.
- Atomicité : slab insuffisant ⇒
EBUSYsans publication partielle (aucun SQE publié). - Sûreté : Miri sur la restitution des buffers des maillons annulés.
- Couverture 100 % lignes + branches.
8. Décisions de fond émergées au Temps 3c
- Sémantique correcte gelée — soft = rompt sur erreur (short read inclus) ; hard = ne rompt pas sur erreur de complétion. (Corrige l’overview.)
- Mode staging sur
submit_*— pas de duplicationprepare_*; usage délimité explicitement parlink_chain(). - Réservation atomique des slots + publication unique — jamais de chaîne
partielle ;
EBUSYen amont si le slab ne suffit pas. link_timeoutexclusivement via le builder — émission isolée refusée (cohérent Temps 2c).skip_cqe_on_successpour les intermédiaires — n’attendre que le résultat final quand c’est le seul utile.
9. Travail à reprendre
Spec suivante : io-uring-3d-multishot.md (opérations multishot : accept/recv/
poll/read multishot, jeton MultishotToken, cycle de vie CQE_F_MORE,
interaction avec les buffers fournis du Temps 3b). Traduction anglaise globale
après validation des documents français.
Licence du document : MPL 2.0
Statut : Spécification technique du Temps 3c (opérations liées) du module air-sys-syscall::io_uring, cible kernel 6.12 LTS.