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 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étion CQE_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_MORE maintenu.
  • 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 -ENOBUFS et termine le multishot (pas de F_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 les events surveillés produit une complétion (into_poll_result() -> PollEvents), F_MORE maintenu.
  • Level-triggered : IORING_POLL_ADD_LEVEL exposé 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_multishot ou 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 (sans F_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 / drapeauFaçadeComplétions
ACCEPT + ACCEPT_MULTISHOTsubmit_accept_multishot[_direct]flux de FD acceptés
RECV + RECV_MULTISHOTsubmit_receive_multishotflux de buffers fournis
READ_MULTISHOT (49)submit_read_multishotflux de buffers fournis
POLL_ADD + POLL_ADD_MULTIsubmit_poll_multishotflux d’événements
TIMEOUT + TIMEOUT_MULTISHOTsubmit_timeout_multishotflux de ticks
ASYNC_CANCELcancel_multishotterminaison

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_multishot sur un listener, N connexions ⇒ N complétions avec F_MORE, fermeture du listener ⇒ complétion terminale ; variante directe (FD dans slots) ; recv_multishot + groupe de buffers, flux de datagrammes, pénurie ⇒ -ENOBUFS terminant le multishot, réapprovisionne + resoumet ; poll_multishot sur 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

  1. MultishotToken distinct — un flux de complétions, pas une op mono-coup ; le type empêche de confondre les deux cycles de vie.
  2. Slot vivant jusqu’à la terminale (!F_MORE) — extension assumée de S1 (comme le NOTIF zero-copy) ; génération anti-CQE-tardif.
  3. Terminaison explicite, réarmement explicite — pas de re-soumission cachée (Principe 7) ; -ENOBUFS signalé distinctement de l’erreur réseau.
  4. accept_multishot_direct privilégié pour les serveurs à fort taux de connexions — connexions en descripteurs directs (lien Temps 3a), pertinent AirCom/compositeur.
  5. 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) :

  1. cancel_multishot synchrone. Implémenté via IORING_REGISTER_SYNC_CANCEL (CancelTarget::Token, annulation synchrone par jeton) plutôt que l’op ASYNC_CANCEL (§6) : cohérent avec le retour Result<()>pas de CQE d’annulation supplémentaire à drainer ; la complétion terminale (-ECANCELED, sans F_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).
  2. poll_multishot edge-triggered seul. IORING_POLL_ADD_LEVEL (1<<3) n’est pas exposé : la signature validée submit_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.