TECHNIK-ARTIKEL

Swift-Compiler-Abstürze auf einem Cloud-Mac systematisch untersuchen

Swift-Compiler-Abstürze auf einem Cloud-Mac systematisch untersuchen

Derselbe Commit wird auf dem Entwicklungsrechner erfolgreich kompiliert, während swift-frontend bei einem kontinuierlichen Build auf einem Cloud-Mac unerwartet beendet wird. Ein erneuter Lauf ist gelegentlich erfolgreich, nach dem Leeren des Caches schlägt plötzlich eine andere Datei fehl. In dieser Situation ist es besonders riskant, gleichzeitig den Quellcode zu ändern, Abhängigkeiten zu aktualisieren und sämtliche Caches zu löschen: Damit verändert sich die gesamte Ausgangslage, und die eigentliche Bedingung für den Compilerabsturz verschwindet möglicherweise. Zuverlässiger ist es, zunächst die Fehlerart zu bestimmen und den Auslöser anschließend nach dem Ein-Variablen-Prinzip einzugrenzen.

Zunächst prüfen, ob der Compiler tatsächlich abstürzt

Ein Fehler bei CompileSwiftSources bedeutet nicht automatisch, dass der Compiler abgestürzt ist. Auch Syntaxfehler, eine fehlgeschlagene Typinferenz oder nicht verfügbare Module führen in dieser Phase zu einem Status ungleich null. Das hier beschriebene Verfahren ist vor allem bei folgenden Anzeichen relevant:

  • Im Protokoll steht ausdrücklich, dass swift-frontend unerwartet beendet wurde, oder es wird signal 11 gemeldet.
  • Die Ausgabe enthält einen Stack dump, eine fehlgeschlagene interne Assertion oder einen Compiler-Stacktrace.
  • Unter ~/Library/Logs/DiagnosticReports/ befindet sich ein zeitlich passender Bericht zu swift-frontend.
  • Derselbe Quellcode und derselbe Befehl lösen den Fehler wiederholt aus; es handelt sich also nicht nur um den einmaligen Abbruch einer Remote-Sitzung.

Zunächst sollten die Informationen zur Toolchain und zum Host erfasst werden. Es genügt nicht, nur die letzten zehn Protokollzeilen zu kopieren:

mkdir -p diagnostics
{
  date -u
  sw_vers
  uname -m
  xcode-select -p
  xcodebuild -version
  xcrun swiftc --version
} | tee diagnostics/environment.txt

find "$HOME/Library/Logs/DiagnosticReports" \
  -maxdepth 1 -type f -name 'swift-frontend*' \
  -exec cp {} diagnostics/ \;

Der Absturzbericht, der vollständige Build-Befehl und der auslösende Quellcode müssen aus demselben Reproduktionslauf stammen. Werden Materialien aus verschiedenen Läufen kombiniert, entstehen häufig falsche Spuren, die sich nicht überprüfen lassen.

Den Ausgangszustand mit einem isolierten Build sichern

Für die Reproduktion sollte ein separates DerivedData-Verzeichnis verwendet und die vollständige Terminalausgabe gespeichert werden. Globale Caches sollten nicht gleich zu Beginn gelöscht werden, da ihre mögliche Beteiligung am Absturz selbst eine noch zu prüfende Variable ist.

set -o pipefail
rm -rf "$PWD/.diagnostics-derived-data"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  -derivedDataPath "$PWD/.diagnostics-derived-data" \
  -jobs 1 \
  build 2>&1 | tee diagnostics/build-single-job.log

Die Parallelität wird hier ausschließlich auf eins reduziert, um festzustellen, ob der Absturz mit paralleler Kompilierung zusammenhängt. Dies ist nicht als dauerhafte Konfiguration gedacht. Stürzt der Compiler auch mit einem einzelnen Job zuverlässig ab, sollte dieses Protokoll aufbewahrt werden. Verschwindet der Fehler, werden bei unverändertem Quellcode und festem DerivedData-Verzeichnis getrennte Tests mit -jobs 2 und dem üblichen Parallelitätswert durchgeführt. Jede Variante sollte mindestens dreimal ausgeführt werden. Dabei ist jeweils zu vermerken, ob der Build erfolgreich war, mit einem gewöhnlichen Compilerfehler endete oder einen Compilerabsturz verursachte; die Angabe „fehlgeschlagen“ allein reicht nicht aus.

Bei der Reproduktion auf einem OVPS Cloud-Mac muss außerdem der tatsächlich ausgewählte Xcode-Pfad erfasst werden. Ein Vergleich der in der Benutzeroberfläche angezeigten Versionsnamen genügt nicht, da sich innerhalb derselben Hauptversion sowohl die Build-Nummer als auch die Swift-Toolchain unterscheiden können.

Eine Vergleichsmatrix mit jeweils nur einer Variablen erstellen

Die Untersuchung sollte mit den kostengünstigsten und am wenigsten invasiven Variablen beginnen. Pro Durchlauf wird nur ein Wert geändert und jeweils eine neue Protokolldatei angelegt.

Vergleichspunkt Basiswert Geänderter Wert Zu klärende Frage
Parallelität -jobs 1 Üblicher Parallelitätswert Tritt der Fehler nur bei paralleler Kompilierung auf?
DerivedData Isoliertes Verzeichnis Neues leeres Verzeichnis Hängt der Fehler von vorhandenen Zwischenprodukten ab?
Kompilierungsmodus Aktueller Projektwert Temporärer Vergleichswert Besteht ein Zusammenhang mit inkrementeller oder Whole-Module-Kompilierung?
Optimierungsstufe Debug-Einstellung Vom Projekt unterstützte Vergleichseinstellung Tritt der Fehler nur während der Optimierung auf?
Toolchain Aktuell festgelegte Version Andere verifizierte Version Handelt es sich um eine Regression einer bestimmten Toolchain?

Xcode sollte nicht gleichzeitig gewechselt, Abhängigkeiten aktualisiert und Caches geleert werden. Verschwindet das Problem nach einem Toolchain-Wechsel, lässt sich daraus lediglich schließen, dass es mit der jeweiligen Toolchain-Kombination zusammenhängt. Dies beweist nicht unmittelbar, dass es in der neuen Version behoben wurde. Auch die fehlgeschlagene Datei, der Kompilierungsmodus und die Anzahl der Auslösungen gehören in dieselbe Aufzeichnung.

Cache-bedingte Fehlschlüsse erkennen

Erst wenn ein leeres DerivedData-Verzeichnis erfolgreich funktioniert, während der Fehler mit dem alten Verzeichnis zuverlässig auftritt, sollten der Modul-Cache und die Build-Datenbank genauer untersucht werden. Das problematische Verzeichnis wird zunächst archiviert; anschließend werden nur gezielte Bereinigungen vorgenommen. Eine sofortige großflächige Löschung nimmt die Möglichkeit, alte und neue Build-Produkte miteinander zu vergleichen.

Die fehlerhafte Datei auf eine minimale Reproduktion reduzieren

Im vollständigen Protokoll muss zunächst der fehlgeschlagene Swift-Kompilierungsschritt ermittelt werden. Lässt sich der Fehler unabhängig vom Projekt reproduzieren, werden die relevanten Deklarationen nach Repro.swift kopiert und zunächst per Typprüfung getestet:

xcrun swiftc -typecheck Repro.swift 2>&1 | tee diagnostics/repro.log

Die Reduktion erfolgt entgegen der Abhängigkeitsrichtung: Zunächst werden nicht relevante Methoden entfernt, danach Protokollimplementierungen, generische Constraints und Property Wrapper. Nach jedem entfernten Abschnitt wird das Prüfsystem erneut ausgeführt. Es ist nicht sinnvoll, anhand des Erscheinungsbilds zu raten, welcher Syntaxabschnitt „kompliziert“ wirkt. Compilerabstürze werden häufig erst durch das Zusammenspiel zweier gewöhnlicher Sprachmerkmale ausgelöst.

#!/bin/zsh
set -o pipefail

output="$(mktemp)"
xcrun swiftc -typecheck Repro.swift >"$output" 2>&1

if grep -Eiq 'signal 11|segmentation fault|stack dump|swift-frontend.*failed' "$output"; then
  cp "$output" diagnostics/latest-crash.log
  rm -f "$output"
  exit 0
fi

rm -f "$output"
exit 1

Dieses Skript definiert das fortgesetzte Auslösen des gesuchten Absturzes als Erfolg. Dadurch eignet es sich für eine manuelle binäre Suche oder Werkzeuge zur Quellcodereduktion. Nach jedem Reduktionsschritt muss außerdem geprüft werden, ob die Fehlersignatur unverändert geblieben ist. Wird aus einer internen Assertion ein gewöhnlicher Typfehler, wurde bereits eine entscheidende Bedingung entfernt.

Falls sich der Fehler nicht mit einer einzelnen Datei reproduzieren lässt, bleibt die kleinstmögliche Modulgrenze erhalten: ein reduziertes Projekt, die erforderlichen Build-Einstellungen, festgeschriebene Abhängigkeitsversionen und genau ein Ausführungsbefehl. Geschäftsdaten, Zugangsdaten und nicht benötigte Ressourcen dürfen nicht enthalten sein.

Ein überprüfbares Übergabepaket erstellen

Mit den abschließenden Materialien sollte sich der Fehler auf einem anderen Cloud-Mac ohne mündliche Erläuterungen überprüfen lassen. Das Verzeichnis sollte möglichst nur Folgendes enthalten:

  • README.md: erwartetes Verhalten, Ausführungsbefehl, Anzahl der Wiederholungen und tatsächliche Ergebnisse.
  • environment.txt: Systemarchitektur, Xcode-Build-Nummer und Swift-Version.
  • Repro.swift oder ein reduziertes Projekt: ausschließlich der erforderliche Quellcode.
  • build.log: ungekürzte Standardausgabe und Fehlerausgabe.
  • Absturzbericht: die zur Ausführungszeit passende Diagnosedatei.
  • check.sh: ein Skript, dessen Exit-Status eindeutig angibt, ob der gesuchte Absturz reproduziert wurde.

Vor der Übergabe wird alles noch einmal in einem neuen Verzeichnis ausgeführt. So lässt sich prüfen, ob das Skript von absoluten Pfaden des ursprünglichen Projekts, einem bestimmten Benutzerverzeichnis oder verbliebenen Modul-Caches abhängt. Tritt das Problem nur bei einer bestimmten Parallelität auf, müssen sowohl der Parallelitätsparameter als auch die Reproduktionswahrscheinlichkeit dokumentiert werden. Ist ausschließlich eine bestimmte Toolchain-Build-Nummer betroffen, gehört auch die erfolgreich funktionierende Vergleichsversion in die Unterlagen. Das Ergebnis ist dann nicht bloß die Aussage „Swift stürzt gelegentlich ab“, sondern eine Sammlung reproduzierbarer, vergleichbarer und für die weitere Fehlerbehebung geeigneter technischer Nachweise.

Häufig gestellte Fragen

Bedeutet ein Fehler bei CompileSwiftSources immer einen Compiler-Absturz?

Nein. Syntax-, Typ- und Abhängigkeitsfehler erscheinen ebenfalls in dieser Phase. Entscheidend sind ein abnormal beendeter swift-frontend-Prozess, ein Signal, ein Stack Dump oder ein passender Absturzbericht.

Muss ein vollständiges Xcode-Projekt als Reproduktion erhalten bleiben?

Nein. Wenn eine einzelne Swift-Datei mit einem swiftc-Befehl zuverlässig abstürzt, ist sie der bessere Testfall. Nur modul- oder buildabhängige Fehler benötigen ein reduziertes Projekt.

OVPS CLOUD-MAC

Den nächsten Build auf einen dedizierten physischen Knoten verschieben

Wählen Sie aus drei Apple-Silicon-Konfigurationen und sechs verfügbaren Knoten. Maßgeblich ist der in der Konsole in Echtzeit angezeigte Status.

Konfiguration auswählen und bestellen