Spec couche 2 — air-event (façade C-ABI de l’event loop async)
Spécification technique — Version 1.0 (décisions validées BDFL 2026-06-25). Couche 2 « Modèle d’objet, IPC, services fondamentaux ».
Position et méthode
air-event est la façade C-ABI de l’event loop asynchrone d’Air. Elle n’est
pas le moteur : le moteur est le runtime air-runtime (couche 1, natif io_uring,
sans tokio — ADR-038,
ADR-039,
spec air-runtime). air-event consomme air-runtime
et l’expose en ABI C stable (libair-event.so, zone air-stable, ABI 10 ans —
ADR-012) pour que C/Swift/Python/Ruby pilotent l’async d’Air sans glue par classe.
Rappel de couches (ADR-039) :
air-runtime(L1) = moteur ;air-event(L2) = façade C-ABI. Un consommateur Rust utiliseair-runtimedirectement (async/await) ; un consommateur polyglotte utiliseair-event(callbacks).
Le défi central : exposer de l’async à complétion en ABI C
Le C ne sait pas .await. Et air-runtime est à complétion avec buffers
possédés (spec air-runtime). La façade doit donc exposer un modèle à callbacks de
complétion (façon libdispatch/libuv), pas async/await :
- une opération async rend un
AirFuture(handle C opaque, refcompté) ; - le consommateur enregistre un callback de complétion (
air_future_on_complete) ; - une boucle (
air_event_loop_run) piloteair-runtimeet invoque les callbacks quand les complétions arrivent ; - annulation :
air_future_cancel→ mappe sur l’annulationair-runtime(ASYNC_CANCEL + reclaim différé) — soundness préservée (le buffer possédé reste vivant jusqu’au CQE d’annulation).
Section 1 — AirEventLoop
typedef struct AirEventLoop AirEventLoop; // opaque
AirEventLoop* air_event_loop_new(void); // possède un exécuteur air-runtime (single-thread)
void air_event_loop_free(AirEventLoop*);
int air_event_loop_run(AirEventLoop*); // bloque, pilote jusqu'à arrêt
int air_event_loop_run_once(AirEventLoop*, int timeout_ms); // un tour (intégration boucle hôte)
void air_event_loop_wakeup(AirEventLoop*); // réveille depuis un autre thread (eventfd interne)
void air_event_loop_stop(AirEventLoop*);
Modèle de thread. Un AirEventLoop = un exécuteur air-runtime single-thread
(ADR-039). Il est piloté par son thread propriétaire (sémantique MainThreadOnly,
cohérente avec les politiques de thread du modèle d’objet air-object, ADR-002).
air_event_loop_wakeup (thread-safe, via un eventfd interne soumis au ring) est le
seul point d’entrée cross-thread.
Intégration boucle hôte (run_once) : un embarqueur (GLib, boucle de jeu, sd-event)
qui possède sa boucle peut soit faire tourner air-event par tours
(run_once), soit attendre l’eventfd de complétion d’air-runtime
(air_event_loop_fd) dans sa boucle (cf. ADR-038 §5 — interop sans epoll côté Air).
Section 2 — AirFuture (complétion type-effacée, C-opaque)
typedef struct AirFuture AirFuture; // opaque, refcompté
// Callback de complétion : reçoit le statut + un AirValue résultat + le contexte user.
typedef void (*AirCompletionFn)(const AirStatus* status, AirValue result, void* ctx);
void air_future_on_complete(AirFuture*, AirCompletionFn cb, void* ctx);
void air_future_cancel(AirFuture*); // → annulation air-runtime (reclaim différé)
void air_future_retain(AirFuture*);
void air_future_release(AirFuture*);
int air_future_is_ready(const AirFuture*);
Type-effacement. AirFuture enrobe un Pin<Box<dyn Future<Output = AirValue>>> côté
Rust + un slot de résultat + la liste de callbacks. Refcompté via le mécanisme
air-object (AtomicU32 + AirHandle, cf. spec air-object) : le handle vit tant qu’un
consommateur le retient (air_future_retain/release).
Résultat = AirValue (union taguée du modèle d’objet air-object). Le callback de
complétion le reçoit +1 (possédé) — règle de propriété uniforme d’air-object
(« retours +1 / arguments +0 ») : à air_value_release après usage. Les erreurs passent
par AirStatus (code + message, cohérent ADR-019/AirError).
Côté Rust, air-event offre aussi un pont AirFuture → impl Future pour que le
code Rust de couche 2+ reste idiomatique (await) tout en partageant la même machinerie.
Section 3 — Timers, signaux, channels (exposés)
Façades C-ABI minces au-dessus de air-runtime-time/-signal/-sync :
AirFuture* air_timer_sleep(AirEventLoop*, uint64_t millis); // → IORING_OP_TIMEOUT
typedef struct AirSignals AirSignals; // signalfd via io_uring
AirSignals* air_signals_watch(AirEventLoop*, const int* signos, size_t n);
AirFuture* air_signals_next(AirSignals*); // complétion = un signal
typedef struct AirChannel AirChannel; // MPSC async borné
AirChannel* air_channel_new(AirEventLoop*, size_t capacity);
AirFuture* air_channel_send(AirChannel*, AirValue v); // back-pressure async
AirFuture* air_channel_recv(AirChannel*);
AirTimer/AirSignal/AirChannel sont des AirObject (refcomptés, introspectables) —
cohérent macro-architecture (collections/notifications observables).
Section 4 — Intégration AirCom et systemd
- AirCom (ADR-001) : toute opération AirCom rend
un
AirFuture.air-eventest le point de coordination async d’AirCom (timers + FD + IPC dans la même boucle). (Détail dans la future specair-aircom.) - systemd (ADR-005) : pont sd-event — un
AirEventLooppeut consommer une sourcesd-event(watchdog,sd_notify) ou être intégré dans une boucle pilotée par sd-event. Permet à un.airservicede coopérer avec systemd sans abandonnerair-event.
Section 5 — ABI C et bindings
libair-event.so, symbolesair_event_*/air_future_*, zoneair-stable(ABI 10 ans, versioned symbols GNU — ADR-012). Tests de conformité ABI en CI.- Header C généré ; bindings Swift (
@dynamicMemberLookup)/Python/Ruby au-dessus du modèle callback. Aucune glue par classe (universel viaAirFuture/AirValue). - Erreurs :
AirStatus(jamais de panic à travers l’ABI —catch_unwindà la frontière, conversion enAirStatus).
Décisions (ratifiées / résolues, BDFL 2026-06-25)
- VALIDÉE — modèle callbacks de complétion (
air_future_on_complete+ bouclerun/run_once) : seul modèle viable pour exposer l’async à complétion en C (cohérent libdispatch/libuv). AirEventLoop= un exécuteur single-thread (ADR-039) ; multi-loop / thread-per-core exposé en C = DIFFÉRÉ v1 (façade dédiée ultérieure).- RÉSOLU —
AirFuturerefcompté viaair-object(AtomicU32+AirHandle), maintenant qu’air-objectest spécifié. - CONFIRMÉ — buffers I/O « possédés » exposés en C (le C fournit un buffer, le
récupère via le callback) — cohérent
air-runtime. - sd-event : direction du pont à cadrer avec la spec
air-systemd(différé v1).
Périmètre v1 / différé
- v1 : boucle single-thread,
AirFuture+ callbacks, timers/signaux/channels, annulation sound, pont RustAirFuture↔Future. - Différé : multi-loop thread-per-core exposé en C, pont sd-event complet (avec spec
air-systemd), bindings polyglottes générés (phase bindings).
Dépendances
air-runtime(couche 1, le moteur),air-object/air-value(modèle d’objet, pourAirValue/refcount — couche 2, spécifié),air-base-lib(erreurs/temps). Aucune dépendance externe. À tracer dansDEPENDENCIES.md.
Stratégie de tests
- Couverture 100 % (Principe 1 ; couche 2 = très élevée, marge selon nature).
- Tests de conformité ABI C (compilation C + appels via
libair-event.so) ; tests depuis C/Swift/Python (le modèle callback fonctionne cross-langage). - Annulation sound :
air_future_cancelmid-vol → pas d’use-after-free du buffer (Miri sur la coucheair-runtimesous-jacente ; tests d’intégration ici). - Intégration : echo réseau async piloté par
air_event_loop_run; timers ; signaux ; back-pressure channel ;run_oncedans une boucle hôte factice. - (Pas de fuzzing dédié : la façade n’ingère pas de données externes ; le décodage est en couche 0/1.)
Décisions de fond
- Façade C-ABI, pas le moteur :
air-event(L2) exposeair-runtime(L1) — couches propres (ADR-039). - Modèle callbacks de complétion : seul moyen d’exposer l’async à complétion en C.
- Annulation sound héritée d’
air-runtime(buffers possédés + reclaim différé, ADR-028). AirValue/AirStatuscomme monnaie d’échange (cohérentair-object/ADR-019).- Pont Rust
AirFuture↔Future: le Rust de couche 2+ gardeasync/await.
Licence du document : MPL 2.0
Statut : v1.0 — décisions validées (2026-06-25). Façade C-ABI du runtime async
air-runtime, s’appuyant sur la spec air-object (AirValue/refcount). Reste à cadrer
avec air-aircom (§4) et air-systemd (sd-event) lors de leurs specs. Implémentation à
suivre. Bases : ADR-001 / ADR-005 / ADR-012 / ADR-038 / ADR-039 + specs air-runtime/air-object.