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 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 utilise air-runtime directement (async/await) ; un consommateur polyglotte utilise air-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) pilote air-runtime et invoque les callbacks quand les complétions arrivent ;
  • annulation : air_future_cancel → mappe sur l’annulation air-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 AirFutureimpl 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-event est le point de coordination async d’AirCom (timers + FD + IPC dans la même boucle). (Détail dans la future spec air-aircom.)
  • systemd (ADR-005) : pont sd-event — un AirEventLoop peut consommer une source sd-event (watchdog, sd_notify) ou être intégré dans une boucle pilotée par sd-event. Permet à un .airservice de coopérer avec systemd sans abandonner air-event.

Section 5 — ABI C et bindings

  • libair-event.so, symboles air_event_*/air_future_*, zone air-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 via AirFuture/AirValue).
  • Erreurs : AirStatus (jamais de panic à travers l’ABI — catch_unwind à la frontière, conversion en AirStatus).

Décisions (ratifiées / résolues, BDFL 2026-06-25)

  1. VALIDÉE — modèle callbacks de complétion (air_future_on_complete + boucle run/run_once) : seul modèle viable pour exposer l’async à complétion en C (cohérent libdispatch/libuv).
  2. 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).
  3. RÉSOLU — AirFuture refcompté via air-object (AtomicU32 + AirHandle), maintenant qu’air-object est spécifié.
  4. 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.
  5. 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 Rust AirFutureFuture.
  • 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, pour AirValue/refcount — couche 2, spécifié), air-base-lib (erreurs/temps). Aucune dépendance externe. À tracer dans DEPENDENCIES.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_cancel mid-vol → pas d’use-after-free du buffer (Miri sur la couche air-runtime sous-jacente ; tests d’intégration ici).
  • Intégration : echo réseau async piloté par air_event_loop_run ; timers ; signaux ; back-pressure channel ; run_once dans 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

  1. Façade C-ABI, pas le moteur : air-event (L2) expose air-runtime (L1) — couches propres (ADR-039).
  2. Modèle callbacks de complétion : seul moyen d’exposer l’async à complétion en C.
  3. Annulation sound héritée d’air-runtime (buffers possédés + reclaim différé, ADR-028).
  4. AirValue/AirStatus comme monnaie d’échange (cohérent air-object/ADR-019).
  5. Pont Rust AirFutureFuture : le Rust de couche 2+ garde async/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.