Jade — Fehlermodell¶
Status: Normativer Entwurf Version: 0.4.0 Datum: 2026-09-23 Geltungsbereich: Die Sorten des Scheiterns in Jade, ihre Abgrenzung voneinander, und die Trap-/Fault-Mechanik, die bisher in keinem Dokument stand.
Verhältnis zu anderen Dokumenten
Dieses Dokument ist ein Dach. Es besitzt die Einteilung und die Grenzen zwischen den Sorten, und es besitzt alles rund um Trap und Fault vollständig. Für die Innenmechanik zweier Sorten verweist es:
| Sorte | Detail steht in |
|---|---|
Throw[E], Row-Algebra, try/catch |
jdl-effect-rows-und-throw |
| Task-Cancellation, Checkpoints, Suspension | jdl-vm-scheduler-spec §3.6 |
| Diagnostics, Query-Abbruch | jade-compiler-db-spec |
Bei Widerspruch mit diesen Dokumenten gilt dieses Dokument für die Abgrenzung und für Trap/Fault, das jeweils andere für die Innenmechanik seiner Sorte.
Kennzeichnung
[N] normativ · [A] ableitbar · [D] Entscheidung dieser Fassung · [O] offen
Regeln dieses Dokuments tragen das Präfix F-. Fremde Regeln werden mit ihrem Herkunftspräfix zitiert (E-3, G-2, S-13, X-10).
Quellenlage
Die Abschnitte 2 bis 4 fassen zusammen, was in jdl-effect-rows-und-throw, jdl-vm-scheduler-spec und 12-design-constitution bereits steht. Die Abschnitte 5 bis 10 sind neu und entstammen einer Entwurfssitzung; sie sind als [D] markiert, wo eine Entscheidung getroffen wurde, und als [O], wo keine gefallen ist.
0. Warum es dieses Dokument gibt¶
Jade hat fünf verschiedene Arten, wie etwas schiefgehen kann, und sie waren über vier Dokumente verteilt, ohne dass eines von ihnen die Grenzen zog.
Das fiel auf, als eine einfache Frage keine Antwort hatte: Was liefert join() zurück, wenn die Task, auf die gewartet wird, getrappt ist? Der Scheduler kennt vier Ausgänge einer Task, das Typsystem hat aber nur für zwei davon einen Platz.
Die Antwort führte in eine Kette weiterer Fragen — was auf einem Trap-Pfad noch aufgeräumt wird, wer mit wem gemeinsam sterben muss, wann der Speicher einer toten Berechnung freigegeben werden darf. Dieses Dokument beantwortet sie oder hält fest, dass sie offen sind.
1. Die Grundunterscheidung¶
Es gibt zwei Fragen, die leicht verwechselt werden.
Die erste: Hat das Programm etwas Erwartbares erlebt? Eine Datei fehlt, eine Eingabe ist ungültig, ein Server antwortet nicht. Das ist normale Programmlogik. Der Code darf darauf reagieren, und tut er es nicht, ist das sein Fehler.
Die zweite: Ist die Berechnung selbst noch gültig? Ein Handle zeigt auf etwas, das es nicht mehr gibt. Ein Codepfad wurde erreicht, den es nicht geben durfte. Hier gibt es nichts zu reagieren, weil die Grundlage weg ist, auf der eine Reaktion stehen würde.
F-1
[N]Die beiden Fragen werden in Jade nie durch denselben Mechanismus beantwortet. Was erwartbar ist, ist ein Wert oder einThrow[E]. Was die Berechnung ungültig macht, ist ein Trap.
Der Grund für die Trennung ist nicht Ästhetik. Sobald eine Sprache erlaubt, einen ungültigen Zustand zu fangen und weiterzumachen, hat niemand mehr eine Zusage darüber, was nach dem Fangen noch stimmt.
2. Die fünf Sorten¶
2.1 Diagnostic — es wird gar nicht erst gebaut¶
Ein Fehler, den die Übersetzung findet. Ein Typ passt nicht, eine Regel der Constitution ist verletzt, ein Name existiert nicht.
Er erreicht die Laufzeit nie. In der CompilerDB ist er eine gültige Antwort auf eine Frage und wird zwischengespeichert wie jede andere: Failed(DiagnosticResult) ist ein Ergebnis, kein fehlendes Ergebnis (jade-compiler-db-spec).
2.2 Fehler als Wert — der Aufrufer bekommt ihn in die Hand¶
Eine Funktion gibt etwas zurück, das entweder das Ergebnis oder die Fehlerbeschreibung enthält. Option und Result in jade::core sind die beiden Grundformen; sie werden vor jedem Plugin in den Seed gelegt.
Nichts am Kontrollfluss ändert sich. Wer das Ergebnis auspacken will, muss beide Fälle behandeln.
2.3 Throw[E] — der Kontrollfluss springt¶
Statt zurückzukehren, springt die Ausführung zu einer vorher feststehenden Stelle. Auf dem Weg dorthin läuft Aufräumcode: Werte werden gedroppt, with-Blöcke geben ihre Ressourcen frei.
Dass eine Funktion das tun kann, steht in ihrer Signatur — das ist das Row-Element Throw[E]. Der einzige eingebaute Effekt dieser Art ist Throw[E]; es gibt keinen zweiten (jdl-effect-rows-und-throw, Invariante 4).
Ob ein Fehler als Wert oder als Sprung erscheint, entscheidet nicht die Funktion, die ihn erzeugt, sondern die Stelle, die ihn benutzt (Regel E-2 ebenda).
2.4 Cancellation — jemand hat das Interesse verloren¶
Eine laufende Arbeit soll aufhören, weil ihr Ergebnis niemanden mehr interessiert. Ein Rennen ist entschieden, eine Frist ist abgelaufen, ein übergeordneter Vorgang wurde beendet.
Das ist kein Fehler. Es ist auch keine Aussage darüber, ob die Arbeit korrekt war — sie wird nur nicht mehr gebraucht.
Cancellation ist kooperativ. Die Task bemerkt sie an festgelegten Punkten und beendet sich dann selbst. Es gibt keine Unterbrechung beliebigen Codes von außen (jdl-vm-scheduler-spec §3.6, S-13).
F-2
[N]Cancellation ist wederThrow[E]noch Trap und ist nichtFallible. Sie trägt kein Row-Element.
2.5 Trap — die Berechnung ist ungültig¶
Eine Ausführungsinvariante ist verletzt. Die betroffene Berechnung darf nicht fortgesetzt werden, aber die Maschine, die das feststellt, ist noch vertrauenswürdig.
Beispiele: ein Handle mit veralteter Generation, ein unzulässiger VM-Zustand, ein erreichtes unreachable, eine verletzte Bytecode-Invariante.
F-3
[N]Ein Trap ist auf Sprachebene nicht fangbar und nicht in einResultmaterialisierbar. Er trägt kein Row-Element, es gibt keincatch Trapund keinResult[T, Trap]. Trap-Behandlung ist Sache der Runtime. (Übernahme vonE-3,jdl-sprachreferenz§9.5.)
Ein Trap wird nicht als JDL-Wert konstruiert. Er entsteht in der Runtime — VM, JME, Scheduler, Intrinsic oder Hostgrenze. JDL kann nur die Bedingung ausdrücken, deren Verletzung dazu führt; unreachable ist die ausdrückliche Form davon.
F-4
[A]Wenn Benutzercode sinnvoll auf ein Ereignis reagieren können soll, ist es falsch als Trap eingeordnet und gehört nach 2.2 oder 2.3.
2.5.1 Traps an der nativen Grenze¶
Der native Ausführungsvertrag (jdl-vm-scheduler-spec v0.3.0 §3.7 bis §3.13, FFI-Spec v0.2.0 §4 und §5) erzeugt zwei neue Trap-Ursachen. Beide sind Fälle, in denen ein nativer Eintrittspfad auf sich selbst warten würde.
F-36
[D]Tritt ein nativer Eintrittspfad mit demselbenNativeEntryTokenerneut in ein exklusives Handle ein, das er bereits belegt, ist das ein Trap. Ein Eintritt mit einem anderen Token ist kein Fehler: Die Task wartet, bis das Handle frei ist.F-37
[D]Fordert ein synchroner Callback native Ausführung in einer AffinityDomain an, die derselbe Token gerade belegt, ist das ein Trap. Die Lane wartet auf den Callback und könnte den Auftrag erst nach dessen Rückkehr ausführen.
Beide erfüllen F-4: Benutzercode kann auf einen Eintritt, der auf sich selbst wartet, nicht sinnvoll reagieren. Deshalb bekommen sie weder ein Throw noch einen Rückgabewert.
Ein Zugriff auf ein Handle, dessen Freigabe bereits angefordert ist, fällt unter die bestehende Ursache „veraltete Generation“ (06-jme-handles-tags §12.5).
F-38
[A]Ein Wartekreis über lose Kommunikation, etwa ein Callback, der auf eine Kanal-Nachricht wartet, während eine andere Task auf das von ihm belegte Handle wartet, ist keine der Sorten dieses Dokuments. Er wird nicht erkannt und endet in keinem Ausgang; die beteiligten Tasks hängen. Er ist ein Programmierfehler. Diagnose darf ihn sichtbar machen (jdl-vm-scheduler-spec§3.11), die Runtime sagt seine Erkennung nicht zu.
2.6 Fatal Fault — die Maschine selbst ist unsicher¶
Die Integrität der Runtime ist möglicherweise verletzt. Dann ist lokale Erholung nicht mehr vertrauenswürdig, weil die Instanz, die erholen soll, betroffen sein kann.
12-design-constitution §11 zählt die legitimen Fälle auf: Ring-0-Korruption, JME-Invariantverletzung, Verifier- oder DescriptorStore-Korruption, nicht wiederherstellbarer VM-interner Zustand. Alles andere ist ausdrücklich kein Freifahrtschein zum Prozessabbruch.
2.7 Übersicht¶
Sorte wann wer reagiert Cleanup
─────────────────────────────────────────────────────────────────────
Diagnostic Übersetzung Autor entfällt
Wert Laufzeit Aufrufer normal
Throw[E] Laufzeit Handler in der Row Unwind-Pfad
Cancellation Laufzeit die Task selbst normal
Trap Laufzeit niemand in JDL nur mechanisch
Fatal Fault Laufzeit niemand nichts
3. Ein Wort, zwei Bedeutungen [O]¶
Cancellation ist im Korpus doppelt belegt, und beide Vorkommen stehen in normativen Dokumenten.
In jade-compiler-db-spec §13 bezeichnet es den Abbruch einer laufenden Query-Auswertung, weil eine neue Revision sie überholt hat. Die Frage bleibt unbeantwortet; es entsteht weder ein QueryResult noch ein DerivationRecord.
In jdl-vm-scheduler-spec §3.6 bezeichnet es das kooperative Ende einer Task.
Die beiden haben verschiedene Auslöser, verschiedene Beobachtungspunkte und verschiedene Folgen. Solange beide Cancellation heißen, ist jede Aussage über „Cancellation" in einem dritten Dokument mehrdeutig.
Offen (§F-O1): Welche der beiden Bedeutungen umbenannt wird. Kandidaten für die CompilerDB-Seite wären
SupersedeoderAbandon, weil dort tatsächlich eine überholte Berechnung verworfen wird; Kandidaten für die Scheduler-Seite gibt es kaum, weilcanceldort der etablierte Begriff jeder vergleichbaren Umgebung ist.
4. Task-Ausgänge und join()¶
Eine Task endet auf genau eine von vier Arten: Success(value), Failure(error), Cancelled, Trap(info) (jdl-vm-scheduler-spec §2.1).
Auf JDL-Ebene sind diese vier nicht gleichwertig.
Success ist der Rückgabewert.
Failure braucht keinen neuen Mechanismus. Eine Task-Grenze ist eine Grenze, über die kein Stack-Unwinding möglich ist, und an solchen Grenzen wird ein Throw[E] als Wert materialisiert; auf der anderen Seite kann daraus wieder lokaler Kontrollfluss werden (B-1, jdl-effect-rows-und-throw §11).
Cancelled überquert die Grenze ebenfalls als Wert, aber nicht auf demselben Weg.
F-5
[D]Cancelledüberquert die Task-Grenze als Wert. Anders als beiFailure(E)entsteht daraus keinThrow[E]; die Behandlung ist Sache der Task- und Join-Abstraktion.
Das folgt aus F-2. Cancellation trägt kein Row-Element, also gibt es keine Kontrollflussform, in die sie zurückverwandelt werden könnte, und es wird auch keine zweite eingeführt.
Reagieren darf die aufrufende Seite selbstverständlich trotzdem. Wer eine abgebrochene Teilarbeit für sich als Fehler wertet, wirft einen eigenen Fehler aus seiner eigenen Row. Das ist die Entscheidung des Aufrufers, keine Rematerialisierung der Cancellation.
Trap überquert die Grenze gar nicht.
F-6
[D]join()liefert niemalsTrapInfo. Ein Trap wird nicht an der Task-Grenze in einen Wert verwandelt. Er propagiert als Trap durch die Ownership-Struktur weiter, bis eine Fault-Grenze erreicht ist (§7).
Damit bleibt F-3 unangetastet. Ohne F-6 hätte der wartende Elternteil einen Trap gefangen und in Daten verwandelt — dasselbe Ereignis, nur mit besserer Tarnung.
F-7
[A]Der statische Ergebnistyp einer Task trägt entsprechend drei Fälle, nicht vier.Offen (§F-O2): Die konkrete Form dieses Typs und die Signatur von
join. ObCancelledein dritter Zweig neben Erfolg und Fehler ist oder anders eingebettet wird, ist Sprachdesign und hier nicht entschieden.
5. Ein Trap wickelt nicht ab¶
Das ist die zentrale Entscheidung dieses Dokuments.
Bei einem Throw[E] läuft auf dem Weg zum Handler Aufräumcode, und dieser Aufräumcode ist JDL. Ein Drop(Custom) ist eine Benutzerfunktion. Ein Scopeable.release ist eine Benutzerfunktion. with senkt zu acquire + Body + release, auch auf Fehlerpfaden.
Nach einem Trap ist genau das nicht mehr zulässig. Die Berechnung wurde für ungültig erklärt; ihren Code danach weiterlaufen zu lassen, wäre die Fortsetzung dessen, was gerade abgebrochen wurde.
F-8
[D]Auf dem Trap-Pfad läuft kein JDL-Code. KeinDrop(Custom), keinScopeable.release, keinwith-Cleanup, kein Handler. Es findet ausschließlich mechanisches Aufräumen durch die Runtime statt.F-9
[N]Die GarantienG-1bisG-5injdl-effect-rows-und-throw§10.2 gelten ausschließlich für den abortiven Pfad, also fürThrow[E]. Sie decken den Trap-Pfad nicht ab. Das war dort implizit über den Begriff „abortiv" und wird hier ausdrücklich.
5.1 Was mechanisch freigegeben werden kann¶
Mechanisch heißt: Die Runtime kennt Besitz und Freigabeoperation vollständig, ohne Benutzercode zu befragen.
Task-lokale MemoryRegion JME nimmt sie am Stück zurück
Refcounted Handle Zähler wird dekrementiert
Scheduler-Registrierungen Waiter, Timer, Wakeups entfernt
Task- und Nursery-State verworfen
Host-Ressource nur wenn Ring 0 Besitz und Reap kennt
5.2 Was nicht mechanisch freigegeben werden kann¶
Transaction.rollback()
flush()
Peer benachrichtigen
protokollspezifisches Herunterfahren
beliebiges JDL-Cleanup
F-10
[A]Eine Ressource, deren sichere Freigabe ausschließlich von JDL-Code abhängt, ist nicht trap-sicher. Wer sie über eine Fault-Grenze hinweg benutzt, hat keine Fault-Grenze.
Der Reap-Pfad kennt nur, was im Register der JME steht. Ein Host-Deskriptor hinter einem Scopeable, dessen release JDL ist, überlebt die Fault-Grenze als Leck.
Offen (§F-O3): Ob daraus die Pflicht folgt, jede Host-Ressource als JME-Handle mit mechanischem Reap zu führen. Das wäre die saubere Antwort und eine erhebliche Auflage an Ring 0.
5.3 Einordnung¶
Das Modell ist nicht ungewöhnlich. C++ ruft bei einer Exception Destruktoren und bei abort keine. Rust unterscheidet panic=unwind von panic=abort. Ein Betriebssystem räumt beim Prozessende Speicher und Deskriptoren mechanisch weg und ruft keine Destruktoren.
Jade macht denselben Unterschied, nur benennt es beide Seiten und legt fest, wann welche gilt.
6. Woher die Runtime weiß, was sie freigeben muss¶
Beim Trap muss die Runtime wissen, welche Handles der sterbende Frame an dieser Stelle noch besitzt. Diese Information existiert heute nicht.
6.1 Keine Laufzeitbuchhaltung¶
Das Registerlayout einer Funktion ist compile-time konstant und liegt im Proto; jeder Slot hat bekannte Größe und Offset, und Handles liegen als HandleId im Buffer (11-jadevalue-und-register-layout §4). Welche Register überhaupt Handles halten können, ist damit bekannt.
Was fehlt, ist die Lebendigkeit: ob ein bestimmtes Register an dieser Stelle noch ein initialisiertes, nicht verschobenes, nicht gedropptes Handle hält. Das weiß zur Laufzeit niemand, und zwar mit Absicht — Use-after-move und Use-after-drop sind in Release Verifier-Fehler, und Poisoning gibt es nur in Debug- und Profiling-Builds (05-vm-instruction-set §6.9).
F-11
[D]Es wird keine Laufzeitbuchhaltung über Registerlebendigkeit eingeführt. Kein Liveness-Bit, das bei jedemMoveundDropmitgeführt wird. Der Registerbuffer bleibt ein flacher Buffer ohne Tags.
Der Grund ist die Kostenverteilung: Diese Buchhaltung liefe in der heißen Schleife, um einen Pfad zu bedienen, der im Idealfall nie genommen wird.
6.2 Fault Ownership Map¶
Stattdessen statische Metadaten.
F-12
[D]Das Proto trägt zusätzlich zum Registerlayout eine Zuordnung von Ausführungspunkt zu besitzenden Handle-Slots. Für jeden trapfähigen Punkt muss der Besitzzustand daraus eindeutig rekonstruierbar sein.
Ob das als Bereichskodierung über PCs, als internierte Root-Sets oder als Besitz-Epochen dargestellt wird, ist Implementierungsarbeit.
Was das kostet, und was es nicht kostet. Es entstehen keine zusätzlichen Kanten im Kontrollflussgraphen, keine Landing Pads und keine Cleanup-Kanten an trapfähigen Aufrufen. Der Gewinn aus K-3/K-4 bleibt damit als Kontrollflussgewinn erhalten.
Er bleibt aber nicht als Metadatengewinn. Die Zuordnung muss jeden trapfähigen Punkt abdecken, und das sind nahezu alle. Das Proto wächst entsprechend.
6.3 Eindeutigkeit an trapfähigen Punkten¶
Die Zuordnung funktioniert nur, wenn eine Operation nicht in einem halbfertigen Besitzzustand trappen kann.
F-13
[D]Jede trapfähige Operation hat einen definierten Umschaltpunkt. Vor ihm gilt der Besitzzustand von vorher, nach ihm der von nachher. Ein Trap dazwischen darf nicht möglich sein. Operationen, die das nicht einhalten können, werden beim Lowering zerlegt.
Das übliche Muster ist: erst prüfen — hier darf getrappt werden —, dann den Besitzübergang festschreiben.
6.4 Der Vertrag gilt auch in Zig¶
Ein Intrinsic ist aus Sicht des Bytecodes eine Instruktion und aus Sicht der Implementierung beliebig viel Zig-Code. F-13 ist damit keine Eigenschaft des Instruktionssatzes allein.
F-14
[D]Ein Intrinsic darf bei einem Trap keinen teilweise festgeschriebenen Besitzzustand hinterlassen. Wo eine Operation das nicht atomar garantieren kann, wird sie in mehrere Intrinsics oder in eine Prepare/Commit-Folge zerlegt. Das ist eine Auflage an jede privilegierte Zig-Implementierung, nicht nur an den Generator.
6.5 Terminwirkung¶
G-7 verlangt, dass die Cleanup-Darstellung feststeht, bevor der Generator geschrieben wird. F-12 bis F-14 gehören zu derselben Frist, weil sie mitbestimmen, was diese Darstellung abdecken muss.
7. Wer mit wem stirbt¶
Ein Trap beendet eine Berechnung. Die offene Frage ist, wie weit das reicht.
7.1 Die Kopplungsfrage¶
Nimm die Laufzeitbeziehungen zwischen Domänen als Kanten. Für jede Kante gilt eine einzige Frage:
Kann eine Seite jederzeit verschwinden, ohne dass die andere für Speicher-, Ressourcen- oder Synchronisationsintegrität noch Mitwirkung von ihr braucht?
F-15
[D]Eine Kante ist fault-safe, wenn der Tod einer Seite die andere entweder nicht beeinträchtigt oder wenn jede verbleibende Abhängigkeit auf der überlebenden Seite als normaler semantischer Fehler erscheinen kann. Andernfalls ist sie fault-coupling.
Fault-safe heißt nicht, dass die Beziehung danach noch benutzbar ist. Es heißt, dass ihr Verlust die Integrität der überlebenden Seite nicht verletzt. Ein Proxy, dessen Gegenseite gestorben ist, liefert von da an nur noch Fehler — der Dienst ist weg, aber niemand steht mit halb geschriebenem Zustand oder einem Schloss da, das nie wieder aufgeht. Das ist der Unterschied zwischen einem Ausfall und einem Schaden, und nur der Schaden koppelt.
Angewandt:
Copy keine verbleibende Abhängigkeit → safe
abgeschlossener Transfer keine verbleibende Abhängigkeit → safe
Proxy PeerGone → Throw[E] → safe
Lease mit Expiry-Regel Ablauf → Throw[E] → safe
robustes Schloss OwnerDied → Throw[E] → safe
fragiles Schloss keine sichere Materialisierung → coupling
geteiltes Handle standardmäßig → coupling
Die zweite Hälfte von F-15 ist kein verkapptes Fangen eines Traps. Wer ein Throw[PeerUnavailable] sieht, beobachtet den Verlust eines Dienstes, nicht das Ereignis in der anderen Domäne.
7.2 FaultDomain¶
F-16
[D]Eine FaultDomain ist die kleinste Menge von Runtime-Domänen, die gemeinsam aufgegeben werden muss, wenn eine ihrer Berechnungen trappt. Sie ergibt sich als Zusammenhangskomponente über die fault-coupling edges.
Sie ist damit abgeleitet, nicht deklariert.
F-17
[N]Fault-Sicherheit ist eine Eigenschaft der materialisierten Beziehung, nicht des Interface-Namens. Ein Binding darf das Ergebnis materialisieren; es darf es nicht behaupten.
Der Grund steht im Context-Modell: Eine Zusage, die Ring 0 nicht trägt, ist Prosa (§11.1). Ein Feld faultSafe: true, das niemand prüft, wäre genau das.
Woraus abgeleitet wird. Bei statisch sichtbarer Implementierung liefert die Labelinferenz schon fast alles: Share(Sync), Share(Send), Own(Unique) und Drop(Custom) werden über einen Solver aus der Struktur bestimmt. 04a-label-inferenz-regeln benennt in C-4 sogar die verdächtige Kombination Share(Sync) + Own(Unique) + not_proven(Drop(Custom)) und legt sie nur deshalb still, weil die formale Behandlung von Advisories fehlt. Fault-Coupling ist im Kern dieses Prädikat mit Konsequenz.
Wo die Ableitung versagt. Bei einem dynamisch geladenen Plugin sieht der Host die Implementierung nicht. Dort gibt es nichts abzuleiten, also muss die Sicherheit aus der Grenze selbst folgen.
F-18
[D]Wo eine Fault-Grenze behauptet wird und die Implementierung der Gegenseite nicht sichtbar ist, sind nur Transferformen zulässig, die konstruktionsbedingt fault-safe sind. Ein geteiltes Handle darf eine solche Grenze nur überqueren, wenn seine mechanische Primitive selbst Owner-Death behandelt.
Damit wird die Tabelle der Bridge-Transferformen in spec-context-model §11.1 von einer Aufzählung zu einer Zulässigkeitsregel.
Offen (§F-O4): Das hängt an
C-11— der Transfersemantik pro Bridge-Form —, das im Context-Modell als offen markiert ist. Solange die geteilte Form über eine Bridge zulässig bleibt, ist keine Bridge eine Fault-Grenze, und die lokale Fehlergrenze für Plugins aus12-design-constitution§11 existiert nicht.C-11ist damit keine Repräsentationsfrage mehr, sondern Vorbedingung.
7.3 Task-Parentage koppelt per Axiom¶
Ein Elternteil, das auf ein Child wartet, hängt von ihm ab. Nach F-15 müsste diese Kante entweder fault-safe sein oder coupling.
Fault-safe kann sie nicht sein: Der Tod des Childs müsste beim Elternteil als semantischer Fehler erscheinen, und genau das verbietet F-6.
F-19
[D]Gewöhnliche Nursery-Parentage koppelt Faults per Axiom. Ein Trap in einem Child faultet den Elternteil und über dessen Parentage weiter. Das wird nicht als Kante im Coupling-Graphen geführt — die Antwort steht bereits in der Ownership-Struktur.F-20
[D]Gewöhnliche Nursery-Parentage darf keine FaultDomain-Grenze überschreiten.
Der Graph aus F-16 beschreibt damit nur zusätzliche Kopplungen zwischen ansonsten isolierbaren Domänen. Er wächst nicht pro spawn.
F-21
[A]Da jede Task über ihre Ownership-Kette an der Root-Nursery ihres Contexts hängt, koppeltF-19alle Tasks eines Contexts. Eine FaultDomain kann deshalb nie feiner sein als ein Context, nur gröber.
Die Untergrenze gilt unbedingt. Ein Handoff nach F-22 macht keine Domäne feiner — er erzeugt einen weiteren Context. Der Context bleibt in jedem Fall der Boden.
Das ist architektonisch erheblich und soll so gemeint sein. Ein ApplicationContext mit zehn völlig unabhängigen Hintergrund-Tasks faultet als Ganzes, sobald eine davon trappt. Die Unabhängigkeit ihrer Aufgaben ändert daran nichts, weil sie über die Ownership-Kette gekoppelt sind und nicht über ihren Zustand.
F-21a
[A]Isolation entsteht nie durch eine weitere Nursery, sondern nur durch einen weiteren Context. Wer eine Nursery aufmacht, strukturiert Lebenszeit; wer isolieren will, materialisiert einen Context.
7.4 Lifetime-Handoff¶
Aus F-20 und F-21 folgt, dass Isolation unterhalb eines Contexts nicht durch Nursery-Struktur entstehen kann.
Der Fall, an dem das sichtbar wird: Eine Accept-Schleife erzeugt Arbeit pro Anfrage. Nach der gewöhnlichen Auflösung gehört diese Arbeit der Nursery der erzeugenden Task. Damit ist sie mit der Accept-Schleife gekoppelt und über deren Parentage mit allem darüber.
F-22
[D]Soll eine Arbeit unabhängig sterben können, muss sie in eine andere fault-isolierbare Ownership-Domäne übergeben werden. Der Übergang trennt gleichzeitig Task-Ownership, Lebenszeitabhängigkeit und Trap-Propagation. Nach der Übergabe ist einjoin()im Sinne strukturierter Nebenläufigkeit nicht mehr zulässig; ein Ergebnis kommt nur noch über eine fault-safe Beziehung zurück.
Damit ist der Lifetime-Handoff kein Komfortmerkmal für langlebige Tasks. Er ist der einzige Weg, auf dem unterhalb des Prozesses überhaupt eine Fault-Grenze entsteht.
Offen (§F-O5): Die Form dieses Handoffs. Aus
F-21folgt, dass die Zieldomäne mindestens ein eigener Context ist — die Übergabe ist damit ein Cross-Context-Vorgang und unterliegtX-10und der Transferform, nicht einer eigenen Handoff-Semantik. Ob das bisher alsTaskSpawner-artig skizzierte Ding damit überflüssig wird oder die Autorität dafür bleibt, ist nicht entschieden.Offen (§F-O6): Die Kosten. Fault-Isolation pro Anfrage heißt nach
F-21ein Context pro Anfrage — Deskriptor materialisieren, Root-Nursery aufsetzen, Scheduler-Governance registrieren. Ob das ein Standardmuster sein kann oder ein Sonderfall für teure Arbeit bleibt, hängt an diesen Kosten.
7.5 Der Graph zur Laufzeit¶
Kanten entstehen und verschwinden, während das System läuft: Context-Aktivierung, Plugin-Load, Bridge-Aufbau, Ende eines Lease.
F-23
[D]Ring 0 führt den Coupling-Graphen zur Laufzeit. Er wird bei Materialisierungs- und Lifecycle-Ereignissen fortgeschrieben, nicht im Scheduler-Hotpath.F-24
[D]Das Entfernen einer Kante kann eine FaultDomain wieder zerfallen lassen. Eine reine Vereinigungsstruktur genügt deshalb nicht.
Ohne F-24 würden Domänen nur gröber, und eine lange laufende Anwendung wäre nach einiger Zeit eine einzige FaultDomain — die Isolation wäre dann ein Startwert und keine Eigenschaft.
Offen (§F-O7): Die Datenstruktur. Zusammenhangskomponenten mit Kantenentfernung sind der teurere Fall; ob eine Neuberechnung der betroffenen Komponente bei jedem Ereignis genügt, hängt daran, wie häufig diese Ereignisse sind.
7.6 Reihenfolge beim Auf- und Abbau¶
Eine Beziehung wird in mehreren Schritten materialisiert. Wenn die Kopplung physisch existiert, bevor die Kante im Graphen steht, und in diesem Fenster trappt etwas, berechnet Ring 0 eine Komponente, die die tatsächliche Kopplung nicht enthält — und gibt eine Domäne frei, deren Zustand noch jemand hält.
F-25
[D]Beim Aufbau wird die Kante registriert, bevor die Beziehung sichtbar wird. Beim Abbau wird die Beziehung zuerst unzugänglich gemacht, dann das Ende der tatsächlichen Kopplung abgewartet, dann die Kante entfernt. In beide Richtungen konservativ.
Am saubersten ist das kein eigener Nebenmechanismus, sondern Teil desselben atomaren Commits, der die Beziehung materialisiert.
F-26
[A]Der mittlere Schritt beim Abbau ist derselbe Übergang wieFaulted → Reclaimablein §8, nur ohne Trap. Er wird mit demselben Begriff beantwortet und nicht mit einem zweiten Mechanismus.
7.7 Prüfbarkeit¶
F-27
[A]Echte Fault-Isolation zwischen zwei Domänen liegt genau dann vor, wenn zwischen ihnen keine fault-coupling edge besteht. Existiert eine, muss Transferform, Ressourcenmodell, Synchronisation oder Isolationsebene geändert werden — oder die Isolation wird nicht behauptet.
8. Ausführungsende und Freigabe sind zwei Zusagen¶
Ein Trap beendet die Ausführbarkeit einer FaultDomain sofort. Er macht ihren Speicher nicht sofort freigebbar.
Der Grund ist fremder Code. Wenn eine Task einen rohen Zeiger in JME-Speicher an eine C-Bibliothek gegeben hat und dieser Aufruf noch läuft, darf dieser Speicher nicht zurückgenommen werden, egal wie tot die Domäne ist.
Für native Handles setzt 06-jme-handles-tags §12.5 das mechanisch um: Ein laufender Aufruf hält sein Handle, eine angeforderte Freigabe wird bis zu seinem Ende verzögert. Die Freigabefunktion eines nativen Handles ist kein JDL-Code und darf deshalb auch nach einem Trap mechanisch ausgeführt werden, ohne F-8 zu verletzen (06 §12.6).
8.1 Hold¶
Was die Freigabe verhindert, ist immer dasselbe: Irgendwer hält die Sache noch fest.
F-28
[D]Ein Hold ist ein Anspruch auf eine bestimmte Ressource, der deren Freigabe verhindert. Er liegt auf der Sache, nicht auf der Domäne. Ein Pin liegt auf einer Region, ein laufender fremder Aufruf auf dem, was er in der Hand hat, ein Lease auf der geleasten Ressource. Wer einen Hold nimmt, gibt ihn zurück.F-29
[D]Eine FaultDomain ist freigebbar, wenn auf keine ihrer Ressourcen mehr ein Hold offen ist. Die Freigebbarkeit der Domäne ist damit abgeleitet und kein eigenes Token, das jemand hält oder setzt.
Das ist nicht nur Wortklauberei. Ein Hold auf Domänenebene wäre grob: Eine einzelne gepinnte Region würde die Freigabe aller anderen Regionen derselben Domäne blockieren, obwohl niemand sie anfasst. Auf Ressourcenebene wird zurückgegeben, was zurückgegeben werden kann, und nur das Festgehaltene bleibt liegen.
Damit braucht der Ablauf keinen eigenen Zwischenzustand:
Running
↓ Trap
Faulted keine JDL-Ausführung, keine neuen Tasks, keine Eintritte
↓ letzter Hold zurückgegeben
Reclaimable
↓
Reaped
F-30
[N]Faultedist nichtReaped. Fault Containment sagt zu, dass sofort nichts mehr ausgeführt wird. Es sagt nicht zu, dass Ressourcen sofort frei werden.
8.2 Was daraus folgt¶
Ein hängender fremder Aufruf hält den Speicher seiner Domäne dauerhaft fest. Die Domäne ist tot und ihr Speicher muss weiter existieren.
Eskalation auf eine größere Domäne hilft nicht, weil der Hold auf derselben Stelle sitzt.
F-31
[A]Starke Fault-Isolation gegen beliebigen nativen Code ist im selben Prozess nicht vollständig zusagbar. Wer sie braucht, braucht eine Isolationsebene, die härter beendet werden kann.
Das deckt sich mit dem bereits festgehaltenen Punkt, dass FFI-Isolation ein Vertrauensmodell ist und keine Leistungsoption.
Ein Hold sagt zwei verschiedene Dinge nicht gleichzeitig zu. Er hält den Speicher am Leben, damit niemand auf Freigegebenes zeigt. Er sagt nichts darüber, ob der Inhalt dieses Speichers noch stimmt. Der Trap kann mitten in einer Änderung passiert sein, und nach F-8 läuft danach kein Code mehr, der sie zu Ende bringt.
F-31a
[A]Ein Hold sichert Speichersicherheit zu, nicht Datengültigkeit. Wer nach dem Tod der besitzenden Domäne noch auf gehaltene Daten zugreift, sieht möglicherweise einen halbfertigen Stand.F-31b
[D]Ein Hold über eine Domänengrenze koppelt genau dann, wenn die Gültigkeit des gehaltenen Zustands nach dem Fault nicht unabhängig zugesichert ist. Ist der Inhalt abgeschlossen und unveränderlich, braucht die haltende Seite keine Mitwirkung mehr und die Kante bleibt fault-safe.
Die Bedingung greift nur, wenn die Unveränderlichkeit zum Zeitpunkt des Faults bereits feststand. Ein Puffer, dessen Abschluss der Trap unterbrochen hat, ist nicht unveränderlich, sondern halbfertig — das ist F-13 eine Ebene höher: Der Übergang von veränderbar zu abgeschlossen braucht einen Punkt, vor dem der eine Zustand gilt und nach dem der andere, und dazwischen darf kein Trap liegen.
Der übliche Fall stellt sich gar nicht erst: Eine Task pinnt ihre eigene Region für ihren eigenen fremden Aufruf, und beide liegen in derselben Domäne.
Offen (§F-O8): Ob Holds eine Disziplin bekommen — etwa eine erklärte Obergrenze für die Dauer eines Pins über eine Fremdgrenze. Ohne sie ist die Lebensdauer einer toten Domäne unbeschränkt.
9. Eintritt von außen¶
Fremder Code, der einen Callback hält, ruft nicht deshalb weniger an, weil die Zieldomäne gestorben ist.
9.1 Zwei verschiedene Fälle¶
Eintritt in eine bereits tote Domäne. Das Trampolin prüft vor dem Eintritt, ob die Zieldomäne noch lebt, und lehnt sonst ab.
Tod während eines laufenden Eintritts. Eine Schwester-Task trappt, während der fremde Thread bereits drin ist und auf Rückkehr wartet. Die Vorabprüfung hilft hier nicht, und der fremde Aufrufer bekommt sonst überhaupt keine Antwort.
F-32
[D]Beide Fälle brauchen ein definiertes Verhalten. Der zweite wird ausdrücklich mitgeführt, weil die Torwächterprüfung des ersten vollständig aussieht und den zweiten stillschweigend offen lässt.
9.2 Die ABI-Bedingung¶
In C kann man nicht trappen. Ein Callback muss zurückkehren.
F-33
[D]Ein Callback darf nur dann auf eine fault-isolierbare Domäne zeigen, wenn sein fremder Vertrag entweder einen fehlgeschlagenen Eintritt ausdrücken kann oder ausdrücklich festlegt, dass ein Eintritt nach dem Tod der Domäne verworfen werden darf.
Ein void (*callback)(int) kann kein Scheitern melden. Er darf eine solche Grenze also nur überqueren, wenn Verwerfen Teil seines Vertrags ist.
Die FFI-Spec v0.2.0 §5 gibt dafür eine Form vor:
- Asynchrone Callbacks kehren ohnehin sofort zurück. Für sie ist Verwerfen die einzige mögliche Reaktion auf einen Eintritt in eine tote Domäne; ihr Vertrag muss es deshalb zulassen
[A]. - Synchrone Callbacks nennen einen Ersatzwert, den die native Seite erhält, wenn die Callback-Task nicht mit
Successendet (FFI §5.4, dort Vorschlag).
Offen (§F-O9): Das Verhalten im zweiten Fall. Ein bereits eingetretener fremder Aufrufer, dessen Zieldomäne unter ihm stirbt, braucht einen Rückgabeweg, den es in seiner Signatur möglicherweise nicht gibt. Vorschlag: der Ersatzwert des synchronen Callback-Vertrags (FFI §5.4). Stirbt die Domäne während des Eintritts, endet die Callback-Task nicht mit
Success, und der fremde Aufrufer erhält den Ersatzwert. Wird der Vorschlag angenommen, ist F-O9 für synchrone Callbacks geschlossen.
10. Synchronisation über Fault-Grenzen¶
Ein gewöhnliches Schloss gibt beim Trap seines Besitzers nicht frei, weil das Freigeben JDL-Code ist und dieser nach F-8 nicht mehr läuft. Wer darauf wartet, wartet für immer.
F-34
[A]Ein gewöhnliches Schloss ist innerhalb einer FaultDomain zulässig und über eine Fault-Grenze hinweg nicht.
Das ist keine hypothetische Sorge. Die Form, in der man einen Guard natürlicherweise schreibt — Disposable mit einem drop, das entsperrt — ist genau die trap-fragile Form. Sie taucht in der erzählenden Runtime-Darstellung bereits so auf; als normative Festlegung ist sie nicht zu verstehen, wohl aber als Hinweis darauf, wohin die Stdlib von selbst laufen würde.
F-35
[D]Eine Synchronisationsprimitive, die über eine Fault-Grenze benutzt werden darf, muss den Tod ihres Besitzers mechanisch erkennen können. Sie gehört damit nach Ring 0, weil nur der Reap-Pfad sie anfassen kann. Sie sieht aus wie ein Stdlib-Typ und ist keiner.
Der Ablauf wäre: Der Besitzer stirbt, Ring 0 markiert das Schloss, Wartende werden geweckt, und die spätere Beobachtung erscheint auf der überlebenden Seite als gewöhnlicher semantischer Fehler. Der Trap selbst wurde dabei nicht gefangen.
Offen (§F-O10): Ob es diese Primitive in der ersten Fassung gibt. Ohne sie gilt
F-34ohne Ausnahme, und geteilter synchronisierter Zustand koppelt immer.
11. Was Ring 0 vor dem Generator klären muss¶
Diese Punkte haben Terminwirkung, weil G-7 verlangt, dass die Cleanup-Darstellung vor dem Generator feststeht.
1. Fault Ownership Map F-12
2. Eindeutigkeit an trapfähigen Punkten F-13
3. Intrinsic-Vertrag F-14
4. Mechanischer Reap-Umfang F-9, F-10, §F-O3
5. Hold-Lebenszyklus F-28 bis F-31
6. Ableitung der Fault-Kopplung F-15, F-17
7. Coupling-Graph mit Zerfall F-23, F-24, §F-O7
8. Registrierungsreihenfolge F-25
9. Eintrittsvertrag von außen F-32, F-33
10. Strukturelle Kopplung als Axiom F-19, F-20
11. Fault-trennender Handoff F-22
12. Robuste Synchronisation F-35, §F-O10
12. Offene Punkte¶
| Nr. | Frage |
|---|---|
| F-O1 | Welche der beiden Cancellation-Bedeutungen umbenannt wird |
| F-O2 | Konkrete Form des Task-Ergebnistyps und Signatur von join |
| F-O3 | Ob jede Host-Ressource ein JME-Handle mit mechanischem Reap sein muss |
| F-O4 | C-11 als Vorbedingung: Transfersemantik pro Bridge-Form |
| F-O5 | Form des Lifetime-Handoffs; Verhältnis zu X-10 und zur Transferform |
| F-O6 | Kosten der Context-Erzeugung und damit die praktikable Isolationsgranularität |
| F-O7 | Datenstruktur für Zusammenhangskomponenten mit Kantenentfernung |
| F-O8 | Disziplin für Holds über Fremdgrenzen |
| F-O9 | Rückgabeweg bei Tod während eines laufenden fremden Eintritts (Vorschlag: Ersatzwert, FFI §5.4) |
| F-O10 | Ob es eine robuste Synchronisationsprimitive in der ersten Fassung gibt |
| F-O11 | Unter welchen mechanisch nachweisbaren Bedingungen ein domänenübergreifender Hold nach F-31b ohne Kopplung zulässig ist — und wer die gehaltene Ressource freigibt, wenn der letzte Hold fällt und ihre Domäne nicht mehr existiert |
Ungelöst aus der Entwurfssitzung, ohne eigene Nummer: Wie die gemeinsame Quelle für Adressierbarkeit (X-10) und Fault-Kopplung dargestellt wird. Beide brauchen dieselbe Beziehungsinformation, aber Adressierbarkeit wird bei jedem Handle-Zugriff geprüft und Fault-Kopplung nur bei Materialisierung und Trap. Eine gemeinsame Darstellung würde die heiße Prüfung an eine Indirektion hängen, die nur die kalte braucht. Der naheliegende Ausweg — eine kanonische Beziehung mit zwei Darstellungen statt einer Darstellung mit zwei Lesern — nimmt allerdings C-11 vorweg, das genau diese Repräsentation noch offen hat.
13. Was dieses Dokument an anderen Dokumenten auslöst¶
Nur aktueller Stand, in der Reihenfolge, in der es sinnvoll ist.
jdl-vm-scheduler-spec — erledigt in v0.3.0: Der Trap-Teardown wartet nicht auf kooperative Cancellation (§3.6, S-13), und dieses Dokument steht in der Bezugsliste.
12-design-constitution v2.1 — §11 macht die Zusage lokaler Fehlergrenzen; FaultDomain ist der Mechanismus dahinter. Ins Glossar §15 gehört FaultDomain, weil dort nur Begriffe mit architektonischem Rang stehen. Hold, Faulted und Reclaimable bleiben in diesem Dokument.
spec-context-model — §11.1 wird von einer Aufzählung der Transferformen zu einer Zulässigkeitsregel; C-11 steigt vom Detail zur Vorbedingung auf.
05-vm-instruction-set und 11-jadevalue-und-register-layout — die Fault Ownership Map ist eine Proto-Erweiterung und betrifft beide.
jade-compiler-db-spec v0.6.0 — betroffen nur durch die Namenskollision aus §3.
Nachgeordnet: jdl-effect-rows-und-throw §10 bekommt einen Verweis auf F-9; die normative Form der Aussage steht hier, weil jenes Dokument den Status einer Arbeitsnotiz trägt. wie-jade-ausfuehrt §14 und Teil A des Addendums sind Erklärung beziehungsweise Nachschlagewerk und folgen, wenn die Specs stehen.
Änderungsprotokoll¶
Version 0.4.0 — 2026-09-23¶
Anschluss an den nativen Ausführungsvertrag. Neu: §2.5.1 mit F-36 (Wiedereintritt mit demselben Token in ein exklusives Handle) und F-37 (synchroner Callback fordert die eigene AffinityDomain an) als Trap-Ursachen; F-38 hält fest, dass ein Wartekreis über lose Kommunikation keine Fehlersorte ist, sondern unerkannt hängt. §8 verweist auf die verzögerte Freigabe nativer Handles und stellt klar, dass deren native Freigabefunktion F-8 nicht verletzt. §9.2 übernimmt die Callback-Formen der FFI-Spec; F-O9 hat einen Lösungsvorschlag (Ersatzwert). §13: Auftrag an die Scheduler-Spec als erledigt vermerkt.
Version 0.3.0 — 2026-09-12¶
Zwei Korrekturen aus der Gegenprüfung.
F-5 war unsauber formuliert und widersprach F-2. Cancelled überquert die Task-Grenze als Wert, wird aber nicht zu Throw[E] rematerialisiert — es gibt keine Kontrollflussform, in die es zurückkönnte. Dass ein Aufrufer daraus einen eigenen Fehler machen darf, ist seine Entscheidung und keine Eigenschaft der Cancellation.
Die Folgerung nach F-31a war zu absolut. Neu als F-31b: Ein domänenübergreifender Hold koppelt nur, wenn die Gültigkeit des gehaltenen Zustands nicht unabhängig zugesichert ist; abgeschlossener unveränderlicher Zustand bleibt fault-safe. Die Unveränderlichkeit muss zum Zeitpunkt des Faults feststehen, was F-13 auf den Abschlussübergang ausdehnt. F-O11 fragt entsprechend nach den nachweisbaren Bedingungen statt nach einem Verbot.
Version 0.2.0 — 2026-09-12¶
Drei Schärfungen aus der Gegenprüfung, keine Rücknahme.
F-15 bekommt die Abgrenzung zwischen Integrität und Verfügbarkeit. Fault-safe heißt nicht benutzbar, sondern unbeschädigt.
F-21 wird als unbedingte Untergrenze ausgesprochen, inklusive der Folge für unabhängige Hintergrund-Tasks. Neu: F-21a — Isolation entsteht durch einen weiteren Context, nie durch eine weitere Nursery.
F-28 und F-29 verlegen den Hold von der Domäne auf die einzelne Ressource; die Freigebbarkeit der Domäne ist daraus abgeleitet. Neu daraus: F-31a — ein Hold sichert Speichersicherheit zu, nicht Datengültigkeit — und F-O11 zu domänenübergreifenden Holds.
Version 0.1.0 — 2026-09-12¶
Erste Fassung. Fasst die Einteilung der Fehlersorten aus bestehenden Dokumenten zusammen und legt die Trap-/Fault-Mechanik erstmals fest.
Neu entschieden: F-5 bis F-6 (Task-Ausgänge), F-8 (kein semantisches Abwickeln beim Trap), F-11 bis F-14 (Ownership-Metadaten statt Laufzeitbuchhaltung, Eindeutigkeit an trapfähigen Punkten, Intrinsic-Vertrag), F-15 bis F-18 (Kopplungsbegriff, FaultDomain, Ableitung statt Behauptung), F-19 bis F-22 (strukturelle Kopplung als Axiom, Handoff), F-23 bis F-26 (Graph zur Laufzeit, Reihenfolge), F-28 bis F-31 (Hold), F-32 bis F-33 (Eintritt von außen), F-35 (robuste Synchronisation als Ring-0-Sache).
Ausdrücklich gemacht, was vorher implizit war: F-9 (G-1 bis G-5 decken den Trap-Pfad nicht ab).
Zehn offene Punkte in §12 festgehalten.