Spec couche 0 — Module io_uring, Temps 3d : opérations multishot
Spécification technique — Version 1.0 (cible kernel : Linux 6.12 LTS)
Position. Le Temps 3d spécifie les opérations multishot : une unique soumission produit plusieurs complétions. Sous-module
air-sys-syscall::io_uring::multishot. Repose sur le drapeau de complétionCQE_F_MORE(axe E) et sur les buffers fournis du Temps 3b (pour recv/read) et les descripteurs directs du Temps 3a (pour accept). Réutilise le cœur du Temps 1. Aucun register opcode, un opcode dédié (READ_MULTISHOT, 49) et des drapeaux d’op (ACCEPT_MULTISHOT,RECV_MULTISHOT,POLL_ADD_MULTI,TIMEOUT_MULTISHOT).
1. Principe et cycle de vie
Une opération mono-coup soumet un SQE, reçoit un CQE, libère son slot. Une opération multishot soumet un SQE et reçoit un flux de CQE :
- chaque complétion intermédiaire porte
CQE_F_MORE: « d’autres complétions suivront pour ce même SQE » ; - la complétion finale (terminaison) n’a pas
CQE_F_MORE: le multishot est terminé (erreur, pénurie de buffers, ou annulation).
Interaction avec le slab S1. Le slot reste vivant tant que les
complétions portent CQE_F_MORE ; il n’est libéré qu’à la complétion sans
F_MORE. C’est le second cas (après le NOTIF zero-copy du Temps 2b) où un
slot survit à sa première complétion — la génération du SubmissionToken
protège contre les CQE tardifs après annulation (Temps 1 §4.2).
Jeton. Une opération multishot rend un MultishotToken (distinct du
SubmissionToken mono-coup). Toutes ses complétions le portent :
#![allow(unused)]
fn main() {
impl Completion {
/// Jeton multishot si cette complétion provient d'un multishot.
pub fn multishot_token(&self) -> Option<MultishotToken>;
// has_more() (Temps 1) = CQE_F_MORE : true tant que le flux continue.
}
}
Réarmement. Quand un multishot se termine (has_more() == false),
l’application resoumet si elle veut continuer. La façade rend la terminaison
explicite ; pas de réarmement caché.
2. Accept multishot
#![allow(unused)]
fn main() {
impl IoUring {
pub fn submit_accept_multishot(&mut self, listener: BorrowedFd<'_>, flags: AcceptFlags)
-> Result<MultishotToken, Errno>;
/// Variante descripteurs directs : chaque connexion atterrit dans un slot
/// auto-alloué de la FixedFdTable (Temps 3a).
pub fn submit_accept_multishot_direct(&mut self, listener: BorrowedFd<'_>, flags: AcceptFlags)
-> Result<MultishotToken, Errno>;
}
}
- Op :
ACCEPT(13) +IORING_ACCEPT_MULTISHOT. Un seul SQE accepte en continu : chaque connexion entrante produit une complétion portant le FD accepté (accepted_fd()),CQE_F_MOREmaintenu. - Variante directe : chaque connexion va dans un slot direct (auto-alloc,
FixedSlotTarget::Alloc) — idéal pour un serveur à très haut taux de connexions (compositeur, AirCom) : pas de FD ordinaire, pas de table userspace à gérer. Lien Temps 3a. - Terminaison : sur erreur (p. ex. listener fermé) ⇒ complétion sans
F_MORE. Cas d’usage : un serveur fait une soumission au démarrage et consomme les connexions au fil de l’eau.
3. Recv / Read multishot (avec buffers fournis)
#![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, opcode dédié). Exigent des buffers fournis (Temps 3b,IOSQE_BUFFER_SELECT) : chaque arrivée de données prend un buffer du groupe et produit une complétion. - Complétion :
into_provided_buffer(group)rend le buffer choisi (id + octets), guard RAII qui réapprovisionne à la libération (Temps 3b §4). La consommation incrémentale (CQE_F_BUF_MORE, Temps 3b §5) se combine au multishot. - Pénurie de buffers : si le groupe est vide à l’arrivée de données, la
complétion porte
-ENOBUFSet termine le multishot (pas deF_MORE). L’application réapprovisionne le groupe puis resoumet. La façade signale ce cas distinctement (terminaison pour pénurie vs erreur réseau). - Intérêt majeur AirCom / serveurs : un seul SQE par connexion sert tout son flux entrant, sans buffer pré-engagé par connexion oisive.
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. Chaque transition d’état du FD vers leseventssurveillés produit une complétion (into_poll_result() -> PollEvents),F_MOREmaintenu. - Level-triggered :
IORING_POLL_ADD_LEVELexposé via une option (PollEvents+ niveau). À défaut, edge-triggered. - Annulation :
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. Émet une complétion à intervalle régulier (timer répétitif) jusqu’à annulation — utile pour un battement périodique dans le reactor sans resoumettre à chaque tick. - Terminaison :
cancel_multishotou erreur.
6. Annulation
#![allow(unused)]
fn main() {
impl IoUring {
pub fn cancel_multishot(&mut self, token: MultishotToken) -> Result<(), Errno>;
}
}
- Annule un multishot en vol (via
ASYNC_CANCEL, Temps 2c, ciblant le jeton). La complétion terminale (sansF_MORE, souvent-ECANCELED) libère le slot ; les CQE tardifs éventuels sont filtrés par la génération (S1). submit_cancel(CancelTarget::…)(Temps 2c) reste utilisable pour des annulations groupées (par FD, par op,Any).
7. Récapitulatif
| Op / drapeau | Façade | Complétions |
|---|---|---|
ACCEPT + ACCEPT_MULTISHOT | submit_accept_multishot[_direct] | flux de FD acceptés |
RECV + RECV_MULTISHOT | submit_receive_multishot | flux de buffers fournis |
READ_MULTISHOT (49) | submit_read_multishot | flux de buffers fournis |
POLL_ADD + POLL_ADD_MULTI | submit_poll_multishot | flux d’événements |
TIMEOUT + TIMEOUT_MULTISHOT | submit_timeout_multishot | flux de ticks |
ASYNC_CANCEL | cancel_multishot | terminaison |
Tous partagent : MultishotToken, le maintien CQE_F_MORE, la libération du
slot à la complétion terminale.
8. Types ajoutés / partagés
Nouveau : MultishotToken (déjà déclaré au Temps 1, sémantique fixée ici).
Réutilise : AcceptFlags, MessageFlags, PollEvents, TimeoutFlags,
ProvidedBufferRing/ProvidedBuffer (Temps 3b), FixedSlotTarget (Temps 3a).
Méthode Completion : multishot_token.
9. Stratégie de tests
- Intégration :
accept_multishotsur un listener, N connexions ⇒ N complétions avecF_MORE, fermeture du listener ⇒ complétion terminale ; variante directe (FD dans slots) ;recv_multishot+ groupe de buffers, flux de datagrammes, pénurie ⇒-ENOBUFSterminant le multishot, réapprovisionne + resoumet ;poll_multishotsur un pipe écrit plusieurs fois ;timeout_multishot(N ticks puis cancel) ;cancel_multishot(terminaison, CQE tardif filtré). - Cycle de vie S1 : le slot reste occupé tant que
F_MORE, libéré à la terminale ; génération filtre les CQE post-annulation (property-based). - Sûreté : Miri sur la restitution du slot multishot ; pas de double libération ; buffers fournis correctement rendus en multishot.
- Couverture 100 % lignes + branches.
10. Décisions de fond émergées au Temps 3d
MultishotTokendistinct — un flux de complétions, pas une op mono-coup ; le type empêche de confondre les deux cycles de vie.- Slot vivant jusqu’à la terminale (
!F_MORE) — extension assumée de S1 (comme le NOTIF zero-copy) ; génération anti-CQE-tardif. - Terminaison explicite, réarmement explicite — pas de re-soumission cachée
(Principe 7) ;
-ENOBUFSsignalé distinctement de l’erreur réseau. accept_multishot_directprivilégié pour les serveurs à fort taux de connexions — connexions en descripteurs directs (lien Temps 3a), pertinent AirCom/compositeur.- recv/read multishot adossés aux buffers fournis — pas de buffer pré-engagé par connexion (lien Temps 3b).
10 bis. Note d’implémentation (PR #46)
Implémentation : sous-module air-sys-syscall::io_uring::multishot (PR #46,
mergée). La surface validée ci-dessus n’a pas été modifiée ; deux choix
d’implémentation sont consignés ici (jamais corrigés en silence — ADR-032) :
cancel_multishotsynchrone. Implémenté viaIORING_REGISTER_SYNC_CANCEL(CancelTarget::Token, annulation synchrone par jeton) plutôt que l’opASYNC_CANCEL(§6) : cohérent avec le retourResult<()>— pas de CQE d’annulation supplémentaire à drainer ; la complétion terminale (-ECANCELED, sansF_MORE) libère le slot. Effet identique, surface inchangée.submit_cancel(ASYNC_CANCEL, Temps 2c) reste disponible pour les annulations groupées (par FD, par op,Any).poll_multishotedge-triggered seul.IORING_POLL_ADD_LEVEL(1<<3) n’est pas exposé : la signature validéesubmit_poll_multishot(fd, events)ne porte pas d’option de niveau. Décision BDFL : conserver l’edge-triggered ; le level-triggered sera reconsidéré par RFC si un consommateur le requiert.
11. Travail à reprendre
Spec suivante : io-uring-3e-shared.md (usage multi-thread : LockedIoUring,
RingPool thread-per-core, SqpollIoUring). Traduction anglaise globale après
validation des documents français.
Licence du document : MPL 2.0
Statut : Spécification technique du Temps 3d (multishot) du module air-sys-syscall::io_uring, cible kernel 6.12 LTS.