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-066 — Descellement additif couche-0 : rt_sigaction non-faute (async réel) + rt_sigpending + trampoline rt_sigreturn ; brique d’install de handler en couche 1

Statut : Accepté (2026-07-04, décision BDFL directe — délivrance async réelle des signaux non-faute autorisée ; sous la délégation des re-sceaux additifs du superviseur, ADR-065). RFC d’extension de couches scellées (ADR-015). Tranche la question laissée ouverte par ADR-051 (§ Périmètre reporté : « rt_sigaction/sigaltstack … nécessite une décision vis-à-vis d’ADR-020 »). Companion et extension d’ADR-064 (doctrine signaux couche 1) ; le sort inerte des 4 fautes reste inchangé (ADR-064 §2). Conventions : ADR-021.

Catégorie : Descellement additif de la couche 0 scellée (couche-0-v1.8couche-0-v1.9) et additif de la couche 1 scellée (couche-1-v1.1couche-1-v1.2). Aucune rupture ; jalons antérieurs conservés.

Contexte

Le toit libc C-ABI et le PAL std::sys (couche 2) ont besoin de sigaction pour les signaux gérables (non-faute : SIGINT/SIGTERM/SIGCHLD/SIGWINCH/ SIGUSR1-2/temps-réel…). Or :

  1. La couche 0 n’expose rt_sigaction que pour les 4 fautes, via synchronous_handler — et inerte (ADR-020/ADR-064 : le noyau force l’action par défaut sur une faute). Aucune voie d’install réel pour un signal non-faute.
  2. Sur x86_64 sans libc, un handler qui revient exige un sa_restorer : à la fin du handler, le noyau saute à cette adresse, dont l’unique rôle est d’appeler rt_sigreturn (restaure le contexte pré-signal). La libc/glibc fournit ce trampoline ; Air, sans libc, doit le fournir lui-même. Sur aarch64, le retour passe par le VDSO — pas de sa_restorer.
  3. sigpending(3) de la libc n’a pas de wrapper couche 0.

Décision BDFL (2026-07-04) : pour les signaux non-faute, la délivrance est async réellert_sigaction installé, le noyau appelle le handler C. Les 4 fautes (SIGSEGV/SIGBUS/SIGFPE/SIGILL) restent inertes → coredump (ADR-064). Cette PR pose les fondations (couches 0 et 1) ; rien en couche 2 n’est écrit ici — elle prépare le terrain.

Décision

1. Additif couche 0 — air-sys-syscall::signal (+ types air-sys-types)

Re-ouverture additive de la couche 0 → re-sceau prévu couche-0-v1.9 :

  • Sous-module async_handler (à côté de synchronous_handler, qui n’est pas touché) :
    • unsafe fn install(signal, handler_sa: usize, flags: SigActionFlags, mask: SignalMask) -> Result<PreviousDisposition, Errno>handler_sa = SIG_DFL(0)/SIG_IGN(1)/adresse de handler ; une fonction dédiée typée (ADR-021 §3), Result/Option, EINTR remonté, sans alloc.
    • unsafe fn restore(signal, previous) -> Result<(), Errno>.
    • Trampoline rt_sigreturn fourni via core::arch::global_asm! (mov $15,%rax; syscall) sur x86_64 uniquement : install pose alors SA_RESTORER (0x0400_0000) + sa_restorer = &trampoline. Sur aarch64, aucun sa_restorer (retour VDSO). Bloc asm justifié // SAFETY: (jamais appelé depuis Rust ; c’est le noyau qui y saute, %rsp sur le sigframe).
  • rt_sigpending() -> Result<SignalMask, Errno> (wrapper simple, 2 arches).
  • Types (air-sys-types::signal::async_handler) : SigActionFlags (bitflags typé — SA_SIGINFO/SA_RESTART/SA_NOCLDSTOP/SA_NOCLDWAIT/… ; SA_RESTORER non exposé, posé en interne) et PreviousDisposition (opaque 32 octets, même buffer ABI que PreviousHandler mais type distinct — étanchéité faute/non-faute).

API entièrement unsafe (contrat async-signal-safe porté par l’appelant, comme synchronous_handler). Réservé aux signaux non-faute : router une faute ici est proscrit — les fautes relèvent de synchronous_handler (inerte).

2. Additif couche 1 — air-signal (brique dual-face)

Re-ouverture additive de la couche 1 → re-sceau prévu couche-1-v1.2 :

  • Module handler : unsafe fn install_handler(signal, disposition, flags, mask) -> AirResult<PreviousDisposition> + unsafe fn restore_disposition(...), où Disposition = Handler(fn-ptr) | Default | Ignore, flags: SigActionFlags (ré-exporté typé). C’est LA brique que la libc (sigaction) et le PAL consomment — jamais la couche 0 en direct (empilement strict ADR-052).
  • pending() -> AirResult<SignalMask> (via rt_sigpending).

Nuance vs ADR-064. ADR-064 §2/§5 écrivait « la couche 1 Rust n’expose AUCUN handler ; tout sigaction vit dans la face libc au-dessus ». Cet ADR précise : la brique d’installation de handler non-faute vit en couche 1 (air-signal, unsafe localisé), la face libc C se contentant de l’habiller en C-ABI au-dessus. La règle inchangée d’ADR-064 : aucun handler de FAUTE en couche 1 (les 4 fautes → défaut). C’est l’unique unsafe exposé d’air-signal ; tout le reste (signalfd, masque, envoi, attente) demeure SAFE.

3. Additif couche 1 — air-thread : primitives futex(2) sur mémoire externe

Module raw_futex : fn futex_wait(word: &AtomicU32, expected, timeout: Option<AirDuration>, scope) -> AirResult<()> et fn futex_wake(word, count, scope) -> AirResult<u32> — ré-export typé (AirResult) de air_sys_syscall::futex. AirMutex/AirRwLock possèdent leur mot ; la libc pthread_mutex_t/cond_t et le PAL opèrent sur de la mémoire opaque fournie par l’appelant C : ces primitives leur permettent de bâtir un objet de synchro sur un &AtomicU32 externe. API SAFE (mot vivant), host-testable. Distinct de runtime_primitives (façade Errno-brute TARGET-ONLY du join d’air-runtime).

Stratégie de test (100 %, sans exception nouvelle, ADR-035)

  • Couche 0 — chemin déterministe via auto-tgkill, dans le thread appelant (pas de fork, donc couverture réelle — pas de CHILD-EXIT) : install un handler posant un AtomicBool → auto-tgkill(self) sur SIGUSR1 débloqué → le noyau exécute le handler et en revient (sur x86_64, atteindre l’assert prouve le trampoline rt_sigreturn) → restore. Sérialisation par Mutex (la disposition est process-wide). SIG_IGN/SIG_DFL (round-trip), bras d’erreur (install(SIGKILL) → EINVAL), rt_sigpending (SIGUSR1 bloqué + généré → pendant, drainé via signalfd avant restauration du masque).
  • Couche 1air-signal : install_handler/restore_disposition (auto-raise, in-thread, Mutex), Default/Ignore, SIGKILL → InvalidInput, pending(). air-thread : futex_wait (WouldBlock/TimedOut/négatif→sondage), futex_wake (0 sans waiter ; rendez-vous 2 threads sur un mot partagé).

Aucune entrée COVERAGE-EXCEPTIONS.md nouvelle n’est requise pour le corps des wrappers (couverts en thread). Seul le décodage d’erreur reste couvert par les tests d’errno existants ; les bras EFAULT-inatteignables suivent le précédent STRUCTURAL de la famille.

Re-sceaux (tags posés par le superviseur, après validation 2 arches)

  • couche-0-v1.9 (async_handler + rt_sigpending + trampoline). Validé sur x86 (barrière) ET aarch64 (CI) — vérification indépendante de tout asm/numéro (NR rt_sigaction 13/134, rt_sigreturn 15, rt_sigpending 127/136) avant push, leçon c_char/x19.
  • couche-1-v1.2 (air-signal install_handler/pending ; air-thread raw_futex) après validation couche 0.

Cet ADR ne crée aucun tag ; il note les re-sceaux planifiés (INDEX + etat-avancement). Sous la délégation d’ADR-065, ces re-sceaux additifs relèvent du superviseur ; l’additif async lui-même a été approuvé BDFL (délivrance async).

Conséquences

  • Débloque la libc sigaction/signal et les handlers non-faute du PAL, sans qu’ils touchent la couche 0 (empilement strict).
  • Réintroduit de l’asm minimal, audité // SAFETY: : dispatch rt_sigaction/ rt_sigpending (2 arches) + trampoline rt_sigreturn (x86_64).
  • air-signal gagne un unsafe exposé (install_handler), assumé et documenté ; air-thread gagne une surface SAFE (raw_futex).
  • Les 4 fautes restent inertes (ADR-064 §2) : aucune régression de doctrine.

Alternatives rejetées

  • signalfd pour tout, y compris les gérables (statu quo ADR-020). Insuffisant pour la compat source C : un programme C appelle sigaction et attend un handler réellement appelé. La face libc l’exige.
  • Exposer l’install de handler seulement dans la libc (couche 2), en tapant la couche 0. Rejeté : viole l’empilement (couche 2 → couche 1 → couche 0). La brique d’install doit vivre en couche 1.
  • sigaction async réel aussi pour les fautes. Rejeté (ADR-064 §2, décision BDFL) : fail-fast + sécurité ; les fautes → coredump.
  • Réutiliser SA_RESTORER de glibc / wrfsbase. Sans objet (pas de libc) ; le trampoline Air est la voie portable correcte, comme arch_prctl pour %fs (ADR-051).
  • FUTEX_WAIT-based objets possédés uniquement (AirMutex). Insuffisant : la libc opère sur mémoire externe ; d’où raw_futex.

Suite

  • Validation couche 0 (x86 barrière + aarch64 CI) → tag couche-0-v1.9 ; puis validation couche 1 → tag couche-1-v1.2 (superviseur).
  • Couche 2 (hors périmètre de cette PR) : la libc bâtit sigaction/signal sur air-signal::install_handler, et pthread_mutex_t/cond_t sur air-thread::raw_futex. Note de re-sceau dans INDEX.md + etat-avancement.md.