ADR-039 — Nommage et placement : runtime async air-runtime (couche 1), modèle d’objet air-object (couche 2), et modèle d’ordonnancement
Statut : Accepté (2026-06-24, validé par le BDFL).
Amende ADR-002 (renomme le crate du modèle d’objet) ;
précise ADR-023 (nom, couche et ordonnancement du
runtime async) et ADR-038 (place la
façade air-event). Relève de la discipline de nommage ADR-029.
Catégorie : Architecture (couches 1/2) / Nommage.
Contexte
Trois incohérences bloquaient la spécification de la couche 2 :
- Collision de nom
air-runtime: la macro-architecture l’emploie pour le modèle d’objet C-ABI (ADR-002, couche 2) ; ADR-023 l’emploie pour le runtime asynchrone. - Ambiguïté de couche du runtime async : ADR-023 le place en couche 1 (il
consomme directement
air-sys-syscall::io_uring) ; la macro-architecture décrit l’event loop en couche 2 (air-event). - ADR-038 a tranché « runtime async natif io_uring, sans tokio » mais sans nettoyer le nom ni la couche.
Décision
1. Le modèle d’objet C-ABI est renommé air-object (couche 2)
Le crate du modèle d’objet (ADR-002) — AirObject/AirClass/AirValue, propriétés
observables, introspection, bindabilité polyglotte — s’appelle désormais air-object
(et air-object-macros). La bibliothèque C devient libair-object.so. Motif :
« runtime » désigne en Rust un runtime d’exécution ; le réserver à l’objet prêtait à
confusion. air-object centre le nom sur son type-clé AirObject.
2. Le runtime asynchrone garde air-runtime (couche 1)
Conforme à ADR-023. Il consomme directement io_uring (couche 0) → son placement en
couche 1 respecte la discipline « une couche ne consomme que la couche d’en
dessous » (un composant qui tape la couche 0 est en couche 1, pas en couche 2). Sémantique
naturelle : air-runtime = le runtime d’exécution async.
Découpage multi-crate (ADR-023, conservé) :
air-runtime-core (scheduler, Task) · air-runtime-io (intégration io_uring) ·
air-runtime-time (timerfd) · air-runtime-signal (signalfd) · air-runtime-sync
(mutex/channel/notify async) · façade air-runtime.
3. air-event (couche 2) = façade C-ABI par-dessus air-runtime
air-event n’est pas le moteur : c’est la façade C-ABI exposable depuis
C/Swift/Python (AirEventLoop/AirFuture/AirTimer/AirSignal/AirChannel) plus
l’intégration AirCom, qui consomme le runtime air-runtime de la couche 1. Couches
propres : air-runtime → couche 0 (L1→L0) ; air-event → air-runtime (L2→L1). Ceci
précise ADR-038 (qui parlait d’« air-event, moteur async couche 2 » : le moteur est
en L1, la façade en L2).
4. Modèle d’ordonnancement : single-thread → thread-per-core, sans work-stealing
Pensé pour le matériel modeste (Raspberry Pi 4B 2/4/8 Go — Principe 9) et « mesurer avant d’optimiser » (Principe 5) :
- Unité de base = un exécuteur single-thread (un ring io_uring). Empreinte mémoire minimale (vital sur 2 Go), zéro synchro inter-cœurs, le plus lisible (Principe 7). Suffit pour la majorité des charges desktop (I/O-bound) et pour tout outil CLI.
- Scaling = thread-per-core shared-nothing (un exécuteur indépendant par cœur,
chacun son ring, tâches non migrantes) quand un daemon en a besoin. Réutilise les
primitives déjà livrées en couche 0 (io_uring Temps 3e, PR #49) :
RingPool(ATTACH_WQ),msg_ring(messagerie inter-ring),LockedIoUring,SqpollIoUring. - Pas de work-stealing en v1 (ADR-023 l’exclut) : on évite le coût mémoire/synchro ; réexamen uniquement si la mesure montre un déséquilibre pénalisant sur charge réelle.
- Travail CPU-bound déporté vers un pool de threads bloquants (
air-thread) pour ne pas bloquer le réacteur.
5. Deux familles de synchronisation, distinctes
- Bloquante :
std::sync/parking_lot, dansair-thread(couche 1). - Asynchrone : mutex/channel/notify de
air-runtime-sync(couche 1).
Cohérent ADR-038 (synchrone-first + async opt-in).
Conséquences
- Noms cohérents :
air-object= système d’objets ;air-runtime= runtime d’exécution async ;air-event= façade C-ABI de l’event loop. - Couches propres : runtime→L0, façade→runtime ; aucun saut de couche.
- Empreinte Pi-friendly : on démarre minuscule (un exécuteur), on scale en ajoutant des réacteurs indépendants, jamais de coût work-stealing.
- Sweep documentaire : la macro-architecture (
air-runtime/libair-runtime.sodu modèle d’objet →air-object/libair-object.so) est mise à jour ; ADR-002 reste valable (il ne nomme pas le crate). La spec de composant du runtime + d’air-events’écrira sur cette base. (L’ajout d’air-runtimeasync à l’inventaire couche 1 de la macro-architecture relève de la passe-2 d’enrichissement.)
Alternatives écartées
- Renommer le runtime async (au lieu du modèle d’objet) : « runtime » est plus
naturel pour l’async ; renommer le modèle d’objet en
air-objectest plus clair et conserve le nommage déjà posé par ADR-023 côté runtime. - Runtime async en couche 2 : violerait la discipline de couches (il consomme io_uring de la couche 0).
- Work-stealing dès la v1 : coût mémoire/synchro sans gain mesuré ; contraire au Principe 5 et à la cible matériel modeste.
Adoption
ADR-039 + sweep macro-architecture (air-runtime → air-object du modèle d’objet) +
entrées registre/INDEX/SUMMARY. Les specs de composant air-runtime (couche 1) et
air-event (couche 2) suivront, sur la base de cet ADR + ADR-023 + ADR-038.