Das größte Speicherleck in Ghostty finden und beheben
(mitchellh.com)- Im Ghostty-Terminalemulator wurde ein schwerwiegendes Speicherleck entdeckt, bei dem bei langem Betrieb mehrere Dutzend GB Speicher belegt werden konnten
- Ursache des Problems war, dass in der nicht standardmäßigen Wiederverwendungslogik für Speicherseiten der
PageList-Strukturmunmapnicht aufgerufen wurde und sich dadurch nicht freigegebener Speicher ansammelte - Weil Claude Code CLI häufig Graph-Ausgaben mit mehreren Codepoints erzeugt, stieg die Nutzung nicht standardmäßiger Seiten stark an, wodurch das Leck in großem Maßstab sichtbar wurde
- Der Fix wurde so umgesetzt, dass nicht standardmäßige Seiten nicht wiederverwendet, sondern sofort freigegeben werden; zur Nachverfolgung und Verifizierung des Lecks wurde außerdem die VM-Tag-Funktion von macOS genutzt
- Der Fix gilt als Behebung des größten Leck-Problems in Ghostty und soll in ein kommendes Release (1.3) aufgenommen werden
Überblick über das Speicherleck in Ghostty
- Einige Nutzer berichteten, dass Ghostty nach langem Betrieb mehr als 37 GB Speicher verwendete
- Das Leck existierte mindestens seit Version 1.0, und erst neuere CLI-Apps erfüllten bestimmte Bedingungen, durch die das Problem sichtbar wurde
- Die Korrektur wurde bereits auf GitHub zusammengeführt und soll in Nightly-Builds sowie im offiziellen Release 1.3 enthalten sein
Aufbau der PageList und Speicherverwaltung
- Ghostty verwendet zum Speichern des Terminalinhalts eine doppelt verkettete Listenstruktur namens PageList
- Jede Seite enthält Daten wie Zeichen, Styles und Hyperlinks
- Seiten werden mit
mmapallokiert und über einen Pool für Seiten in Standardgröße wiederverwendet- Seiten bis zur Standardgröße werden an den Pool zurückgegeben
- Seiten mit nicht standardmäßiger Größe müssen direkt mit
munmapfreigegeben werden
- Die Struktur selbst ist korrekt, doch ein Bug in der Optimierungslogik führte zum Leck
Scrollback-Optimierung und Ursache des Bugs
- Wenn Ghostty das
scrollback-limitüberschreitet, wird eine Optimierung ausgeführt, bei der die älteste Seite wiederverwendet wird- Dadurch steigt die Performance, weil keine neue Seite allokiert und nur Pointer angepasst werden müssen
- Das Problem war, dass dabei nur die Metadaten einer nicht standardmäßigen Seite auf Standardgröße geändert wurden, während der tatsächliche Speicher unverändert blieb
- Bei der späteren Freigabe wurde sie dadurch fälschlich als Standardseite behandelt, sodass
munmapnicht aufgerufen wurde
- Bei der späteren Freigabe wurde sie dadurch fälschlich als Standardseite behandelt, sodass
- Infolgedessen wurden nicht standardmäßige Seiten nicht freigegeben und sammelten sich an, was bei langer Laufzeit zu einem massiven Speicherleck führte
Claude Code und das großflächige Sichtbarwerden des Lecks
- Claude Code CLI erzeugt häufig Graph-Ausgaben mit mehreren Codepoints, wodurch die Nutzung nicht standardmäßiger Seiten zunimmt
- Zudem entsteht viel Scrollback-Ausgabe, sodass sich das Leck schnell ansammelt
- Nach dem Design von Ghostty sollten nicht standardmäßige Seiten nur selten auftreten, doch durch die Eigenschaften von Claude Code ließ sich das Leck in großem Umfang reproduzieren
- Der Entwickler stellte klar, dass dieser Bug kein Problem von Claude Code ist, sondern ein Fehler in der internen Logik von Ghostty
Inhalt des Fixes
- Die Lösung besteht darin, nicht standardmäßige Seiten nicht wiederzuverwenden, sondern sofort mit
munmapfreizugeben- Wird beim Scrollback eine nicht standardmäßige Seite gefunden, wird stattdessen eine neue Standardseite aus dem Pool erneut allokiert
- Einige Nutzer schlugen eine Strategie zur Wiederverwendung nicht standardmäßiger Seiten vor, derzeit wurde jedoch bewusst zunächst ein einfacher und sicherer Fix umgesetzt
- Beispielcode für die Änderung:
if (first.data.memory.len > std_size) { self.destroyNode(first); break :prune; }
Nachverfolgung des Lecks mit VM-Tags
- Mit der VM-Tag-Funktion des Mach-Kernels unter macOS wurden den Speicherallokationen der
PageListspezifische Tags zugewiesen- Dadurch lassen sich beim Debugging die Speicherbereiche von Ghostty klar identifizieren
- Das half erheblich dabei, die Ursache des Lecks zu finden und den Fix zu verifizieren
- Mit dieser Funktion ließ sich visuell prüfen, ob
PageList-bezogener Speicher freigegeben wurde
System zur Vermeidung von Speicherlecks in Ghostty
- Ghostty erkennt und verhindert Speicherlecks auf verschiedene Weise
- In Debug-Builds und Unit-Tests wird Zigs Leak-Detecting-Allocator verwendet
- In der CI werden alle Tests mit valgrind ausgeführt
- Mit macOS Instruments werden Leaks im Swift-Code geprüft
- GTK-bezogene PRs werden mit Valgrind-GUI-Tests verifiziert
- Dieses Leck trat nur unter bestimmten Bedingungen auf und ließ sich daher mit den bisherigen Tests nicht reproduzieren
- Ein neuer Testfall wurde hinzugefügt, um Regressionen zu verhindern
Fazit
- Bei diesem Vorfall handelt es sich um das bislang größte bekannte Speicherleck in Ghostty
- Auch nach dem Fix soll die Situation durch Nutzerberichte und Reproduktionstests weiter überwacht werden
- Die von der Community bereitgestellten Diagnosedaten und Reproduktionsfälle spielten eine entscheidende Rolle bei der Lösung des Problems
- Es wird betont, dass eine reproduzierbare Umgebung der Schlüssel zur Behebung von Speicherlecks ist
1 Kommentare
Hacker-News-Kommentare
Wirklich erfreuliche Neuigkeiten. Großer Applaus an alle, die an der Behebung des Problems mitgewirkt haben
Der Bug wurde bereits letzte Woche in diesem Thread erwähnt
Claude Code scheint der Auslöser gewesen zu sein, durch den dieser Bug für mehr Nutzer sichtbar wurde, aber es gab auch Leute wie mich, die dasselbe Problem hatten, obwohl sie Claude Code überhaupt nicht benutzt haben
Die Kriterien dafür, wann eine Seite als „nicht standardmäßig (non-standard)“ eingestuft wird, sind weniger schwarz-weiß als gedacht
Ich vermute auch, dass das Leak bei Leuten mit Einstellungen wie
scrollback-limit = 0häufiger aufgetreten sein könnteEs ist etwas schade, dass die Art der Behebung unnötigerweise nicht standardmäßige Seiten löscht und neu erstellt; vielleicht hätte man alte Seiten, die bereits nicht standardmäßig waren, wiederverwenden können
Die Funktionsweise von PageList war schon immer dieselbe, und selbst als der Bug vorhanden war, wurde bei der Kapazitätsanpassung nur eine falsche Größe gesehen
Bei der wahrgenommenen Performance wird es keine Änderung geben
Die vorgeschlagene Alternative wurde ebenfalls geprüft, aber der aktuelle Ansatz ist durch Benchmark-Daten ausreichend gestützt
Ich kann meine Meinung dazu noch ändern, aber diesmal habe ich mich darauf konzentriert, das Leak zu beheben, statt das gesamte Modell umzustellen
Es war tatsächlich ein reproduzierbarer Bug, der einen segfault ausgelöst hat
Mehr als alles andere in den letzten 20 Jahren lässt es die CLI wieder neu wirken
Ausgezeichneter Artikel. Danke an mitchellh dafür, Ghostty gebaut zu haben
Ich bin letztes Jahr umgestiegen und habe es nicht bereut
Etwas überraschend fand ich nur, dass der Fix erst in einem Feature-Release in einigen Monaten enthalten sein soll
Ich hätte erwartet, dass er in einem Bugfix-Release landet
Sobald von Seiten die Rede war, dachte ich sofort: „Ah, Memory Pooling“, und dann: „Wahrscheinlich ein Ringpuffer“ — und tatsächlich ging es um Scrollback-Wiederverwendung
Ich konnte auch die Position des Bugs direkt erraten — es war der Teil, in dem der Seitenspeicher nicht korrekt freigegeben wurde
Das Diagramm zur Speicherausrichtung war ebenfalls großartig
Es erinnert einen wieder daran, dass jeder neue Versuch auch die Möglichkeit eines Leaks mit sich bringt
Ich bin diese Woche zu Ghostty gewechselt und hatte bei der Entwicklung einer Terminal-UI-App einen OOM-Crash
Die Struktur nutzte UTF8-Symbole in der Tab-Leiste, und beim Ändern der Terminalgröße kam es sofort zum Crash
Es war leicht zu reproduzieren, deshalb war ich gerade dabei, einen Bugreport vorzubereiten, als mir auffiel, dass es dem im Blogpost beschriebenen Problem sehr ähnlich sieht
Ich hoffe, dass es damit behoben ist
Ich habe @mitchellh gefragt, mit welchem Tool die Speichervisualisierung erstellt wurde, und da die Website auch mobil gut funktioniert, war ich auch auf den Stack neugierig
Der Code für die Visualisierung war einmalig, deshalb wurde eher die Korrektheit als die Qualität geprüft
Für jeden Blogpost wird ein eigener Namespace getrennt gehalten und nicht wiederverwendet
Es wurde nur überprüft, dass die Implementierung nichts Merkwürdiges tut (z. B. Bitcoin-Mining, Geheimnisse preisgeben usw.)
Entscheidend ist die Informationsvermittlung, und solche Diagramme machen den Inhalt deutlich leichter verständlich
Ich verfolge die Entwicklung von Ghostty weiterhin
Es wirkt stellenweise etwas nach Overengineering, aber solche Bug-Postmortems sind für Menschen, die Handwerkskunst lieben, äußerst wertvolles Material
Ich frage mich, wie ein Rust-basierter Terminal so etwas ohne Performance-Einbußen implementieren würde
Selbst ohne viel über Ghostty oder Terminal-Emulatoren zu wissen, war der Artikel leicht verständlich
Die Zugänglichkeit und die freundliche Erklärung waren beeindruckend
Mir wurde wieder bewusst, wie wichtig ein reproduzierbarer Bugreport ist
Ich warte darauf, dass jemand sagt: „Mit Rust wäre das nicht passiert“
Rust garantiert auf Sprachebene keine „Leak-Sicherheit“ (leak safety)
Auch sicherer Rust-Code kann Speicher leaken — nur ist das kein Sicherheitsproblem
Sogar in der Standard-API ist ein Leak ausdrücklich erlaubt, etwa mit Box::leak
Rust macht es lediglich schwieriger, unbeabsichtigte Leaks zu erzeugen, verhindert sie aber nicht vollständig