# Überlebt die Reihenfolge in der Freigabewarteschlange einen Wettlauf?

Eine menschliche Freigabe bedeutet wenig, wenn ein späterer Agent-Aufruf an einem früheren vorbeispringen kann, weil seine Karte zufällig zuerst angeklickt wurde. Wenn zwei Schreibvorgänge um denselben entfernten Zustand konkurrieren, muss das Gateway ihre Reihenfolge festlegen, bevor jemand eine der beiden Karten berührt. Diese Reihenfolge muss bei der Weiterleitung und im Audit-Eintrag erhalten bleiben.

Das übersieht man leicht, weil der Erfolgsfall aus einer Karte, einem Klick und einer erfolgreichen Antwort besteht. Der Fehler tritt auf, wenn zwei Agent-Prozesse fast gleichzeitig Anfragen senden, ein Prüfer die sichtbaren Karten in einer ungünstigen Reihenfolge freigibt und das Ziel die Anfrage akzeptiert, die es zuerst erreicht. Ich habe erlebt, dass Teams dieses Verhalten als Nebenläufigkeit bezeichneten. Tatsächlich handelt es sich nur um eine undefinierte Entscheidung, die getroffen wird, nachdem der Mensch geglaubt hat, er hätte sie kontrolliert.

## Die Reihenfolge beginnt mit der Annahme durch das Gateway

Die Reihenfolge der Aufrufe ist die Abfolge, in der das Aktions-Gateway Anfragen für dieselbe konfliktbehaftete Ressource in eine Spur aufnimmt. Sie entspricht weder der Reihenfolge, in der Karten dargestellt werden, noch der Reihenfolge, in der jemand klickt, eine Netzwerkverbindung geöffnet wird oder Antworten aus der Ferne eintreffen.

Gib jedem angenommenen Aufruf ein unveränderliches Ticket, etwa `42`, `43` und `44`. Speichere das Ticket zusammen mit der Anfragebeschreibung, der Identität des aufrufenden Prozesses, dem Ziel und der Kennung der Spur. Das Ticket muss vorhanden sein, bevor das Gateway eine Freigabe anfordert. Andernfalls kann die Oberfläche nur melden, welche Anfrage der Prüfer zufällig zuerst gesehen hat. Das reicht nicht aus, um einen Wettlauf zu rekonstruieren.

Eine Spur umfasst den Bereich, in dem eine veränderte Reihenfolge das Ergebnis verändern kann. Zwei Anfragen, die dieselbe Deployment-Umgebung überschreiben, gehören in eine Spur. Zwei Aufrufe, die denselben entfernten Incident-Eintrag ergänzen, gehören ebenfalls in eine Spur. Eine Anfrage zum Abrufen von Paketmetadaten und eine Anfrage zum Aktualisieren eines separaten Testdienstes müssen möglicherweise nicht aufeinander warten. Jede Aktion global zu ordnen wirkt sicher, macht aber eine unabhängige langsame Anfrage zum Ausfallgrund für jeden Agenten.

Der schwierige Teil besteht darin, die Spur ehrlich festzulegen. Ein Ziel-Hostname ist oft zu weit gefasst. Ein bloßer Endpunktpfad ist oft zu eng. `PATCH /documents/7` und `POST /documents/7/publish` betreffen dasselbe Dokument, obwohl sich ihre Pfade unterscheiden. Wenn dem Gateway Informationen fehlen, um diese Beziehung abzuleiten, ordne die Aktionen derselben konfigurierten Spur zu. Tu nicht so, als würde der entfernte Dienst ein Reihenfolgeversprechen reparieren, das das Gateway nie gegeben hat.

Der Annahmeeintrag sollte ungefähr so aussehen:

```text
ticket=42 lane=release-prod event=accepted action=write-release caller=agent-a
ticket=43 lane=release-prod event=accepted action=write-release caller=agent-b
```

Diese Zeilen beantworten später eine präzise Frage: Für welchen Aufruf hat das Gateway zuerst die Verantwortung übernommen? Sie behaupten nicht, dass Ticket 42 zuerst abgeschlossen wurde. Ein langsames Ziel kann dazu führen, dass Ticket 43 je nach zulässigem Ausführungsmodell früher oder später fertig wird. Das Gateway muss dieses Modell benennen, statt dem Journal zu erlauben, es nachträglich zu erfinden.

## Eine Freigabe ist eine Entscheidung, keine Erlaubnis zum Überholen

Wenn ein Prüfer Anfrage B vor Anfrage A entscheidet, wird B dadurch nicht früher angenommen. B hat lediglich eine Bedingung erfüllt und wartet nun, bis es an der Reihe ist.

Diese Unterscheidung verschwimmt, weil Freigabeoberflächen gewöhnlich jede Karte als isolierte Aufforderung behandeln. Das funktioniert bei einer Aktion ohne Folgen für die Reihenfolge. Bei konkurrierenden Schreibvorgängen scheitert es. Wenn die Oberfläche beide Karten aktiv lässt, muss ein Klick auf B B in einen freigegebenen Wartestatus versetzen und nicht direkt an den Dispatcher weitergeben. Der Dispatcher prüft das erste Ticket in der Spur. Er leitet nur das früheste Ticket weiter, für das eine Zulassungsentscheidung vorliegt.

Für das erste Ticket hat ein Gateway drei sinnvolle Ergebnisse:

- Die Freigabe erlaubt die Weiterleitung dieses Tickets.
- Eine Ablehnung wird als endgültige Verweigerung protokolliert, danach wird das nächste Ticket freigegeben.
- Ablauf oder ausdrücklicher Abbruch werden als abschließendes Ergebnis protokolliert, danach wird das nächste Ticket freigegeben.

Ein viertes Verhalten verursacht Probleme: Eine spätere Freigabe wird sofort weitergeleitet, weil für das frühere Nachbarticket noch keine Entscheidung vorliegt. Das wirkt entgegenkommend, weil der Prüfer schnell ein Ergebnis erhält. Zugleich richtet es die entfernte Reihenfolge nach dem Zeitpunkt eines UI-Ereignisses. Jemand kann A geöffnet haben, um die Parameter zu prüfen, und B als harmlose Aufräumaktion freigeben. Das System sendet dann B zuerst, obwohl es das Paar als Warteschlange dargestellt hat.

Die Oberfläche sollte den Zustand sichtbar machen. Die erste Karte kann Approve und Reject anbieten. Spätere Karten können ebenfalls eine Entscheidung annehmen, sollten aber anzeigen, dass sie hinter Ticket 42 warten. Alternativ kann die Oberfläche ihre Steuerelemente zurückhalten, bis frühere Tickets abgeschlossen sind. Beide Ansätze können die Reihenfolge bewahren. Der erste gibt dem Prüfer mehr Kontrolle, der zweite ist schwerer misszuverstehen. Die Oberfläche darf niemals den Eindruck erwecken, dass jeder positive Klick eine sofortige Ausführung auslöst.

Sallyports Autorisierung pro Sitzung und seine Freigabesteuerung pro Aufruf machen die Anfragegrenze sichtbar. Die Reihenfolge braucht jedoch weiterhin ein Ticket, das vor der Freigabeentscheidung vergeben wird. Ein Prüfer kann keine Warteschlange beurteilen, wenn das Produkt nur eine ungeordnete Sammlung von Karten speichert.

## Den Wettlauf mit nachvollziehbaren Schreibvorgängen reproduzieren

Ein sinnvoller Test schickt zwei unabhängige Agent-Prozesse in eine gemeinsame Schreibspur und sorgt dafür, dass das Ziel die Ankunftsreihenfolge protokolliert. Verwende nicht zwei Lesevorgänge, zwei idempotente Statusprüfungen oder zwei Anfragen, die voneinander unabhängige Datensätze aktualisieren. Solche Tests können bestehen, obwohl die Warteschlange Überholvorgänge zulässt.

Verwende einen entbehrlichen HTTP-Endpunkt, der einen POST-Body annimmt und das empfangene Ticket an eine Datei oder Datenbanktabelle anhängt. Er sollte das empfangene Ticket zurückgeben. Für diese Übung braucht das Ziel keine Authentifizierung, wenn es nur unter einer lokalen Testadresse läuft. Es geht darum, den Freigabe- und Weiterleitungspfad des Gateways zu testen, nicht die Übergabe von Zugangsdaten.

Dieser kleine Node-Server erzeugt ein einfaches Ankunftsprotokoll:

```js
const fs = require("node:fs");
const http = require("node:http");

http.createServer((request, response) => {
  let body = "";
  request.on("data", chunk => { body += chunk; });
  request.on("end", () => {
    const item = JSON.parse(body);
    fs.appendFileSync("arrival.log", `${item.ticket} ${item.value}\n`);
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify({ received: item.ticket }));
  });
}).listen(8787);
```

Konfiguriere eine freigegebene Aktion, die `POST /write` an diesen Endpunkt sendet und das Anfrage-Ticket in ihrem JSON-Body überträgt. Starte zwei neue Agent-Prozesse, statt einen Prozess zwei Aufrufe nacheinander ausführen zu lassen. Jeder Prozess sendet dieselbe Aktion mit einem anderen Wert, zum Beispiel `A` und `B`. Sorge dafür, dass beide Aufrufe eine Freigabe benötigen und die Karten gemeinsam ausstehend bleiben.

Die erwarteten ersten Belege sind unspektakulär und eindeutig:

```text
Sessions
42 accepted agent-a write A
43 accepted agent-b write B

Approval cards
42 write A
43 write B

arrival.log
(empty)
```

Wenn die Karten in umgekehrter Ticketreihenfolge erscheinen, halte an und untersuche den Vorgang. Eine umgekehrte Darstellung beweist noch keine falsche Ausführung, lädt den Prüfer aber dazu ein, auf Grundlage einer falschen Geschichte zu handeln. Wenn immer nur eine Karte erscheint, halte fest, ob die zweite Anfrage bereits ein Ticket besitzt und ob das Journal besagt, dass sie hinter der ersten wartet. Die zweite Karte zu verbergen ist in Ordnung. Ihre Existenz zu verbergen ist es nicht.

Führe diese Übung viele Male aus. Starte A zuerst und danach B, dann B zuerst und danach A. Starte beide so nah beieinander, wie es dein Test-Harness erlaubt. Füge eine kurze Verzögerung ein, nachdem einer der Prozesse das Gateway betreten hat, damit die Planung weniger zufällig ist. Wiederholte Überschneidungen sind nötig, weil ein einzelner erfolgreicher Lauf oft nur einen günstigen Thread-Zeitplan widerspiegelt und keine erzwungene Regel.

## Karten-, Weiterleitungs- und Abschlussreihenfolge unterscheiden sich

Der Test sollte drei Reihenfolgen erfassen, denn sie beantworten unterschiedliche Fragen und sollten nicht in einer einzigen Spalte zusammenfallen.

Die Kartenreihenfolge ist die Reihenfolge, in der der Prüfer ausstehende Entscheidungen sehen kann. Innerhalb einer Spur sollte sie der Ticketreihenfolge folgen, selbst wenn die Anwendung die Karten in getrennten Durchläufen der Ereignisschleife zeichnet. Eine Karte kann einen Ankunftszeitpunkt anzeigen, aber das Ticket ist das entscheidende Feld.

Die Ausführungsreihenfolge ist die Reihenfolge, in der das Gateway angenommene Aktionen an den HTTP- oder SSH-Kanal weitergibt. Bei einer strikt serialisierten Schreibspur sollte das Ziel Ticket 42 vor Ticket 43 sehen. Das Gateway sollte unmittelbar vor der Übergabe der Anfrage an den Kanal ein `dispatched`-Ereignis ausgeben. Leite die Weiterleitung nicht aus einem Antwort-Ereignis ab. Ein Ziel kann einen Schreibvorgang empfangen, den Zustand ändern und dann die Verbindung verlieren, bevor es eine Antwort sendet.

Die Abschlussreihenfolge ist die Reihenfolge, in der Antworten, Timeouts oder Transportfehler zurückkehren. Sie kann von der Weiterleitungsreihenfolge abweichen, wenn das System überlappende Aufrufe in getrennten Spuren zulässt. Selbst in einer Spur kann eine asynchrone Implementierung den Abschluss verspätet protokollieren, weil sie Logs erst nach der Netzwerkbereinigung schreibt. Die Abschlussreihenfolge ist operativ nützlich, darf aber die kausale Reihenfolge nicht überschreiben.

Ein kompakter Testeintrag macht die Unterscheidung schwer übersehbar:

```text
42 accepted
43 accepted
42 card-shown
43 card-shown
43 approved
42 approved
42 dispatched
42 succeeded
43 dispatched
43 succeeded
```

Dass `43 approved` in diesem Lauf vor `42 approved` erscheint, ist zu erwarten. Dass `42 dispatched` vor `43 dispatched` erscheint, ist der Vertrag. Wenn die Anwendung nur endgültige Erfolge protokolliert, verschwinden beide Tatsachen. Dann kann ein Ermittler nicht feststellen, ob der Benutzer B zuerst freigegeben hat, ob der Dispatcher A überholt hat oder ob das Ziel zwei bereits freigegebene Anfragen neu geordnet hat.

RFC 9110 unterscheidet sichere HTTP-Methoden von Methoden, die Zustandsänderungen anfordern. Diese Unterscheidung ist hier wichtig: Für Beobachtungen kann ein Gateway eine lockerere Reihenfolge zulassen, während Schreibvorgänge einen ausdrücklichen Nebenläufigkeitsvertrag brauchen. RFC 9110 liefert keine brauchbare Gesamtreihenfolge über zwei unabhängige Client-Verbindungen hinweg. Sobald es eine Freigabe vor den Aufruf setzt, liegt diese Entscheidung beim Gateway.

## Die zweite Karte zuerst freigeben und zurückhalten

Der wichtigste grundlegende Wettlauftest gibt B absichtlich zuerst frei. Damit wird geprüft, ob die Implementierung die Freigabe als Zustandsänderung oder als direkten Sende-Button behandelt.

Starte mit den Tickets 42 und 43, die in derselben Spur ausstehen. Gib Ticket 43 frei. Die Karte oder das Journal sollte in einen Zustand wechseln, der `approved, waiting for 42` entspricht. Das `arrival.log` des Ziels muss leer bleiben. Gib danach Ticket 42 frei. Das Ziel sollte `42 A` gefolgt von `43 B` empfangen, und der Aktivitätseintrag sollte beide Weiterleitungen in dieser Reihenfolge zeigen.

Wiederhole denselben Test, wobei Ticket 42 abgelehnt wird. Das erwartete Zielprotokoll enthält nur B. Das Journal enthält weiterhin beide Tickets:

```text
42 accepted
43 accepted
43 approved
42 rejected reason=user
43 dispatched
43 succeeded
```

Eine Ablehnung ist ein Aktionsergebnis und kein Ausbleiben einer Aktion. Wenn das Journal sie auslässt, sieht jemand, der das Protokoll später prüft, dass B ausgeführt wurde, ohne Erklärung für die fehlende frühere Anfrage. Das führt zu falschen Vermutungen: Vielleicht hat ein Angreifer die Freigabe umgangen, vielleicht hat die App Daten verloren, vielleicht hat ein Prüfer etwas freigegeben, das er nicht gesehen hat.

Teste danach einen Timeout. Lass die Freigabe von A ablaufen, während B bereits freigegeben wurde. Die Anwendung sollte den Ablauf von A einmal protokollieren, B ausführen dürfen und B weiterleiten. Sie darf nicht sowohl `expired` als auch `rejected` erzeugen, weil ein Hintergrund-Timer und ein verspäteter Klick miteinander konkurriert haben. Wähle mit einem atomaren Compare-and-Swap beim Ticketstatus genau eine abschließende Entscheidung. Das unterlegene Ereignis muss erkennen, dass das Ticket bereits aufgelöst wurde, und nichts tun.

Brich schließlich B ab, nachdem es freigegeben wurde, aber bevor A aufgelöst ist. B darf niemals weitergeleitet werden. Sein abschließender Zustand muss cancelled lauten, und eine spätere Auflösung von A darf B nicht wiederbeleben. Damit lässt sich ein häufiger Fehler in Warteschlangen erkennen: Der Dispatcher speichert eine alte Liste freigegebener Tickets und sendet später einen Eintrag, nachdem ein Abbruch-Handler ihn aus der sichtbaren Warteschlange entfernt hat.

Ein guter Test prüft jedes Ergebnis einzeln. Begnüge dich nicht mit einer einzigen Behauptung, dass das Ziel den erwarteten Endzustand erhalten hat. Ein Endzustand kann nach einer falschen Reihenfolge korrekt aussehen, wenn B A überschreibt, Wiederholungen ein Duplikat verbergen oder das Ziel seine eigene Konfliktregel anwendet.

## Das Ticket bei Wiederholungen und langsamen Zielen erhalten

Eine Anfrage behält ihren Platz in der Warteschlange, wenn der Kanal sie wiederholt. Nach einem Verbindungsfehler ein neues Ticket zu erstellen, verändert die Bedeutung der Freigabe und kann dazu führen, dass eine spätere Anfrage sie überholt.

Angenommen, Ticket 42 wird weitergeleitet, die TCP-Verbindung schließt, bevor das Gateway eine Antwort erhält, und das Ziel hat den Schreibvorgang möglicherweise angewendet oder auch nicht. Ticket 43 wartet. Dem Gateway stehen mehrere Richtlinien offen: ein unbekanntes Ergebnis melden und anhalten, mit einem Idempotenz-Token wiederholen, wenn das Ziel dies unterstützt, oder eine neue menschliche Entscheidung verlangen. Es darf 42 nicht stillschweigend fallen lassen und 43 senden, als hätte 42 nie existiert.

Die richtige Richtlinie hängt von der entfernten Operation ab. Ein Endpunkt, der eine Idempotenzkennung akzeptiert, kann eine Wiederholung sicher genug machen, um das ursprüngliche Ticket weiterzuverwenden. Ein blind ausgeführter Befehl über SSH kann das oft nicht. Melde in diesem Fall für 42 ein unbekanntes Ergebnis, halte die Spur blockiert oder gib sie nach einem menschlichen Eingriff ausdrücklich auf und protokolliere den Grund. 43 automatisch freizugeben kann den Schaden vergrößern, wenn 43 davon ausgeht, dass 42 fehlgeschlagen ist.

Teste dies mit einem Ziel, das A empfängt, den Eingang protokolliert und die Antwort schließt, bevor der HTTP-Austausch abgeschlossen ist. Das Gateway sollte eine Spur wie diese bewahren:

```text
42 accepted
42 approved
42 dispatched attempt=1
42 outcome=unknown
43 accepted
43 approved waiting-for=42
```

Ob Ticket 43 letztlich ausgeführt wird, ist eine dokumentierte betriebliche Entscheidung. Unzulässig ist ein Journal, das behauptet, 42 sei fehlgeschlagen, bevor das Gateway dafür Belege hat, gefolgt von einem erfolgreichen B, das sich auf diese Behauptung stützt.

Langsame Anfragen legen einen separaten Implementierungsfehler offen. Ein Dispatcher, der beim Warten auf die entfernte Antwort einen Mutex hält, serialisiert möglicherweise versehentlich alles, auch unabhängige Spuren und die Benutzeroberfläche. Ein Dispatcher, der seinen Reihenfolgezustand zu früh freigibt, lässt das nächste Ticket vorpreschen. Halte den Annahme- und Weiterleitungszustand der Spur klein, speichere das Weiterleitungsereignis dauerhaft, übergib die Anfrage an den Kanal und warte dann auf das Ergebnis, ohne ein weiteres Ticket derselben Spur passieren zu lassen, wenn eine strikte Serialisierung gilt.

## Das Audit-Journal muss die Kausalität bewahren

Ein hashverketteter Audit-Eintrag beweist, dass gespeicherte Einträge nicht unbemerkt verändert wurden. Er macht eine verwirrende Ereignisfolge jedoch nicht automatisch verständlich. Das Ereignismodell braucht weiterhin genug Informationen, um einen gleichzeitigen Freigabe-Wettlauf zu erklären.

Protokolliere ein Annahmeereignis, bevor irgendeine Karte erscheint. Protokolliere eine Freigabeentscheidung mit Ticket und Akteur oder Interaktionsmethode. Protokolliere die Weiterleitung, bevor die HTTP-Anfrage oder der SSH-Befehl in seinen Kanal gelangt. Protokolliere das abschließende Kanalergebnis, ohne einen früheren Eintrag zu ersetzen. Jedes Ereignis braucht zusätzlich zur Uhrzeit eine eigene Anhängeposition.

Wanduhren sind beim Debugging nützlich, können die Reihenfolge aber nicht allein definieren. Zwei Ereignisse können dieselbe Zeitauflösung haben, Uhren können sich verstellen und eine Anwendung kann Schreibvorgänge aus verschiedenen Threads in eine Warteschlange stellen. Eine Anhängeposition oder Sequenznummer legt fest, in welcher Reihenfolge das Journal die Ereignisse angenommen hat. Das Ticket pro Anfrage legt die vorgesehene Reihenfolge in der Spur fest. Bewahre beides auf.

Vergleiche bei der Zwei-Schreibvorgänge-Übung diese Fakten:

- Die Annahmepositionen zeigen 42 vor 43.
- Die Freigabeereignisse können 43 vor 42 zeigen.
- Die Weiterleitungspositionen zeigen 42 vor 43, wenn beide Aufrufe erfolgreich sind.
- Die Ankunftsdatei des Ziels zeigt 42 vor 43.
- Abschließende Ergebnisse sind an dieselben Tickets angehängt und ersetzen sie nicht.

Sallyport erzeugt die Ansichten Sessions und Activity aus einem einzigen verschlüsselten, hashverketteten Audit-Log. Mit `sp audit verify` kann diese Kette offline über dem Chiffretext geprüft werden. Dadurch wird die Auswahl der Ereignisse besonders wichtig: Die Prüfung kann zeigen, dass Einträge nicht verändert wurden, während Ticket und Ereignistypen dem Menschen erklären, was tatsächlich passiert ist.

Sortiere das sichtbare Journal nicht nach Abschlusszeit und nenne es dann Historie. Das stellt einen Netzwerkwettlauf als Entscheidungsfolge dar. Sortiere die primäre Zeitleiste nach der dauerhaft gespeicherten Anhängeposition und zeige daneben Ticketnummer und Zeitstempel. Eine gefilterte Ansicht für einen Agent-Prozess sollte die ursprünglichen Positionen bewahren, damit der Prüfer erkennen kann, dass eine andere Anfrage zwischen zwei Ereignissen gewartet hat, ohne raten zu müssen.

## FIFO gilt für konfliktbehaftete Wirkungen, nicht für jedes Byte

Eine FIFO-Warteschlange ist ein Versprechen über miteinander kollidierende Wirkungen. Sie ist kein Grund, jede Netzwerkoperation durch einen einzigen Thread zu zwingen oder harmlose Lesevorgänge hinter einem ungelösten Schreibvorgang zu verzögern.

Beginne mit der Klassifizierung jeder Aktion. Ein Deployment-Befehl, der eine gemeinsame Umgebung verändert, braucht eine Spur. Ein HTTP-POST, das eine Abrechnungsaktion erstellt, braucht eine Spur, möglicherweise pro Konto. Ein Abruf, der ein statisches Artefakt liest, kann oft unabhängig laufen. Ein SSH-Befehl, der nur den Festplattenspeicher meldet, ist möglicherweise rein beobachtend. Sei trotzdem vorsichtig: Befehle können durch Shell-Startdateien, temporäre Dateien oder entfernte Befehls-Wrapper versteckte Wirkungen haben. Behandle mehrdeutige Befehle als Schreibvorgänge, bis du ihre Wirkungen beschreiben kannst.

Die verbreitete Alternative lautet, jede Freigabe sei unabhängig, weil der Prüfer die Anfrage ansehen kann. Das klingt attraktiv, weil es das Design einer Warteschlange überflüssig macht. Es scheitert, wenn der Prüfer jede Anfrage einzeln versteht, aber keinen Hinweis erhält, dass B davon ausgeht, A sei bereits passiert. Menschliche Prüfung ersetzt keinen fehlenden Serialisierungskontext.

Der entgegengesetzte Fehler ist eine einzige globale Warteschlange. Sie macht die Reihenfolge einfach, lässt die Anwendung aber festgefahren wirken, sobald ein entfernter Host stockt. Getrennte Spuren brauchen klare Namen, eine stabile Ressourcenauswahl und Journalfelder, die sie identifizieren. Wenn du nicht erklären kannst, warum zwei Aktionen dieselbe Spur teilen, kannst du auch das Reihenfolgeversprechen nicht testen.

Verwende die Freigabe pro Aufruf nicht als Ersatz für Spuren. Ein Klick für jede Nutzung hilft einem Menschen, jede Aktion zu beurteilen. Er definiert jedoch nicht, ob zwei freigegebene Aktionen aneinander vorbeiziehen dürfen. Das sind unterschiedliche Kontrollen mit unterschiedlichen Fehlerbildern.

## Den Wettlauf zum Release-Kriterium machen

Ein Fehler in der Warteschlange erscheint selten als offensichtlicher Absturz. Später zeigt er sich als unerklärlicher entfernter Zustand, als Freigabe, die scheinbar für die falsche Aktion galt, oder als Journal, das eine Untersuchung nicht abschließend klären kann. Behandle die Zwei-Schreibvorgänge-Übung als Release-Test für jeden Aktionstyp, der einen gemeinsamen Zustand verändern kann.

Das Release-Kriterium sollte mehr als erfolgreiche Antworten prüfen. Erzeuge überlappende Aufrufe, kehre die Freigabereihenfolge um, lehne die erste Anfrage ab, lass sie ablaufen, brich die zweite Anfrage ab und erzwinge nach der Weiterleitung ein unbekanntes Ergebnis. Speichere für jeden Fall die Ticketfolge, die Kartenstatus, die Weiterleitungsereignisse, das Ankunftsprotokoll des Ziels und die Journaleinträge.

Wenn einer dieser Datensätze nicht mit den anderen übereinstimmt, widerstehe dem Impuls, das als Darstellungsproblem abzutun. Ein Darstellungsproblem kann das erste sichtbare Zeichen dafür sein, dass verschiedene Teile der Anwendung unterschiedliche Definitionen von Reihenfolge gewählt haben. Korrigiere den Vertrag bei der Annahme und sorge dann dafür, dass Karte, Dispatcher, Zieltest und Journal denselben Vertrag melden.

Die erste praktische Maßnahme ist klein: Füge einer konfliktbehafteten Schreibspur ein unveränderliches Ticket hinzu und lasse deinen Test zuerst das spätere Ticket freigeben. Wenn diese Anfrage das Ziel vor ihrem früheren Nachbarn erreicht, kontrolliert die Freigabewarteschlange die Arbeit, deren Freigabe sie behauptet, noch nicht.
