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.8 →
couche-0-v1.9) et additif de la couche 1 scellée (couche-1-v1.1 →
couche-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 :
- La couche 0 n’expose
rt_sigactionque pour les 4 fautes, viasynchronous_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. - 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’appelerrt_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 desa_restorer. 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éelle — rt_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é desynchronous_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_sigreturnfourni viacore::arch::global_asm!(mov $15,%rax; syscall) sur x86_64 uniquement :installpose alorsSA_RESTORER(0x0400_0000) +sa_restorer = &trampoline. Sur aarch64, aucunsa_restorer(retour VDSO). Bloc asm justifié// SAFETY:(jamais appelé depuis Rust ; c’est le noyau qui y saute,%rspsur 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_RESTORERnon exposé, posé en interne) etPreviousDisposition(opaque 32 octets, même buffer ABI quePreviousHandlermais 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>(viart_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 defork, donc couverture réelle — pas de CHILD-EXIT) : install un handler posant unAtomicBool→ auto-tgkill(self)sur SIGUSR1 débloqué → le noyau exécute le handler et en revient (sur x86_64, atteindre l’assert prouve le trampolinert_sigreturn) →restore. Sérialisation parMutex(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é viasignalfdavant restauration du masque). - Couche 1 —
air-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 (NRrt_sigaction13/134,rt_sigreturn15,rt_sigpending127/136) avant push, leçonc_char/x19.couche-1-v1.2(air-signalinstall_handler/pending; air-threadraw_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/signalet les handlers non-faute du PAL, sans qu’ils touchent la couche 0 (empilement strict). - Réintroduit de l’asm minimal, audité
// SAFETY:: dispatchrt_sigaction/rt_sigpending(2 arches) + trampolinert_sigreturn(x86_64). air-signalgagne ununsafeexposé (install_handler), assumé et documenté ;air-threadgagne une surface SAFE (raw_futex).- Les 4 fautes restent inertes (ADR-064 §2) : aucune régression de doctrine.
Alternatives rejetées
signalfdpour tout, y compris les gérables (statu quo ADR-020). Insuffisant pour la compat source C : un programme C appellesigactionet 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.
sigactionasync réel aussi pour les fautes. Rejeté (ADR-064 §2, décision BDFL) : fail-fast + sécurité ; les fautes → coredump.- Réutiliser
SA_RESTORERde glibc /wrfsbase. Sans objet (pas de libc) ; le trampoline Air est la voie portable correcte, commearch_prctlpour%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 → tagcouche-1-v1.2(superviseur). - Couche 2 (hors périmètre de cette PR) : la libc bâtit
sigaction/signalsurair-signal::install_handler, etpthread_mutex_t/cond_tsurair-thread::raw_futex. Note de re-sceau dansINDEX.md+etat-avancement.md.