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

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 :

  1. 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.
  2. 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).
  3. 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-eventair-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, dans air-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.so du 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-event s’écrira sur cette base. (L’ajout d’air-runtime async à 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-object est 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-runtimeair-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.