ハクソク

世界を動かす技術を、日本語で。

Ghosttyの最大メモリリークを特定して修正する

概要

Ghosttyで発生していた大規模なメモリリークの原因と修正内容の解説。
メモリ管理の仕組みと、バグが発生した経緯の説明。
Claude Codeを契機にリークが顕在化した背景。
修正方法と今後の対策についての考察。
再発防止策やコミュニティの貢献への謝辞。

Ghosttyのメモリリーク問題と修正

  • 数ヶ月前からGhosttyで極端なメモリ消費の報告が相次いだ事例。
  • 一部ユーザーが10日間の稼働で37GB消費を確認。
  • 本件の原因究明修正内容の概要説明。
  • Ghostty 1.0以降から存在したバグだが、Claude CodeなどのCLIアプリにより顕在化。
  • 修正済みであり、nightlyリリース1.3タグにて提供予定。

Ghosttyのメモリ管理構造

  • PageListというデータ構造で端末内容を管理。
  • PageListは双方向リンクリストで、各ノードがページ(文字・スタイル・ハイパーリンク等)を保持。
  • ページは標準サイズ非標準サイズの2種類。
    • 標準ページ:プールから取得・解放時はプールに戻す
    • 非標準ページ:直接mmapで確保・解放時はmunmap呼び出し
  • mmapの頻繁な呼び出しを避けるため、メモリプールを活用。

スクロールバック最適化とバグの発生

  • スクロールバック制限に達すると、最古のページを再利用する最適化を実装。
  • 再利用時、メタデータのみ標準サイズにリセットし、実際のメモリは非標準のまま残存。
  • 解放時、標準ページと誤認しmunmapが呼ばれず、メモリリーク発生。
  • 非標準ページは本来稀だが、Claude Codeの出力仕様で大量発生。

修正内容

  • 非標準ページは再利用せず、解放時に必ずmunmapを実行。
  • スクロールバック時に非標準ページを検出した場合は、新規標準ページをプールから取得。
  • macOS向けに仮想メモリタグを導入し、PageListのメモリを特定しやすく改善。

再発防止策

  • Zigのリーク検出アロケータをデバッグビルド・ユニットテストで利用。
  • ValgrindmacOS InstrumentsでGUIやGTKパスも検証。
  • 今回のリーク再現用のテストケースを新規追加。
  • 再現性のあるバグ報告が修正の鍵。

コミュニティへの謝辞

  • @grishyによる再現手順の提供と分析への感謝。
  • 詳細な診断を行ったコミュニティメンバーへの謝意。
  • PageListが問題箇所であることを特定するための重要なヒントの提供。

Ghosttyの今後のメモリ管理戦略

  • 現在は標準ページを基本とし、最適化・単純化を優先。
  • 非標準ページの再利用や動的調整は今後の課題として検討。
  • より複雑な戦略(利用頻度に応じた調整等)は追加調査後に検討予定。

まとめ

  • Ghostty史上最大級のメモリリークが修正された事例。
  • 今後もメモリ関連の報告を注視し、再現性のある問題には迅速に対応予定。
  • コミュニティの協力がバグ修正の大きな原動力となった事例。

Hackerたちの意見

これは素晴らしいニュースだね!解決に協力してくれたみんな、お疲れ様!先週ここでユーザーが指摘してた問題だったよね。 https://news.ycombinator.com/item?id=46460319 Claude Codeがこのバグを引き起こす原因になったかもしれないけど、実際にClaude Codeを使ったことがないのにこのバグに遭遇してた人もいるんだよね。ページが非標準になる理由が、思ってるほど単純じゃないのかも。 それに、scrollback-limit = 0とか、すごく小さい値を使ってる人には、もっと頻繁に漏れが発生してたのかな?大したことじゃないかもしれないけど、新しいページが非標準である必要がある場合、修正が不要に非標準ページを削除して再作成しちゃうみたいだね。最も古いページ(削除が必要なやつ)がすでに非標準で再利用できるのに。
> 大したことじゃないかもしれないけど、新しいページが非標準である必要がある場合、修正が不要に非標準ページを削除して再作成しちゃうみたいだね。これはブログ記事で触れられてるよ。PageListはずっとこういう風に動いてきたし、バグがあった時もそうだった。容量調整中にサイズが間違って表示されることがあったからね。これでパフォーマンスに影響は出ないはずだよ。ブログ記事でも言ったけど、君が提案したような代替アプローチもあるけど、今の見解(標準サイズが一般的)は既知のベンチマークでしっかりサポートされてるから、見解を変えるための十分な実証データがないんだ。ここで考えを変えることにはオープンだけど、世界観を変えつつ漏れを修正するのは避けたかったんだ。
メモリリークに関するスレッドはこちらだよ: https://news.ycombinator.com/item?id=46461061
信頼できる再現性ってめっちゃ貴重だよね。
@mitchellh メモリのビジュアライゼーションには何を使ったの?見た目がいいし、ウェブサイトはモバイルでもちゃんと動くね。スタックは何?
Opus 4.5で生成された静的HTML/CSSだよ。ビジュアライゼーションにはAIを使うのが好きなんだ。一度きりの使い捨てコードだから、品質は全然気にしない(完全にバカじゃなければいいけど)、メンテナンスも必要ないしね。自分が専門知識を持ってるトピックだから、最終結果は正確さをしっかり確認するよ。再利用できない図をブログ記事ごとに名前空間を分けて作ってる(他の投稿では使われないように)。実装が…ビットコインをマイニングしたり、秘密を漏らしたりしてないかだけは確認してる(自分のサイトには秘密はないから)。その後は、品質には全然こだわらないよ。伝えたい情報が重要で、こういう図があるとみんなにとって消化しやすくなるんだ。
Ghosttyやターミナルエミュレーターに不慣れな人にとって、すごくわかりやすい説明だね。ありがとう!
いいまとめだね。それと、mitchellh、Ghosttyをありがとう!去年切り替えたけど、後悔してないよ。ただ、修正が数ヶ月後の機能リリースに取っておかれてるのにはちょっと驚いてる。バグ修正リリースに含まれると思ってたから。
面白いタイミングだね。今週Ghosttyに移ったばかりなのに、今日ターミナルUIアプリを開発してるときにOOMクラッシュに遭遇したよ。偶然にも、このTUIにはこんな感じのタブバーがあって、認識しやすさとアクティビティインジケーターのためにUTF8アイコンを使ってるんだ(ここでは©と€をプレースホルダーとして使ってる):1|Flakes © 2|Installed © 3|Store © € 4|Security © € ────────────────────────────────────────────────────────────── 普通はこれで問題ないんだけど、ターミナルのサイズを変更するとすぐにクラッシュが発生しちゃう。避けるのは簡単だけど、やっぱりイライラするよね!簡単に再現できるバグレポートを出そうと思ってたけど、ブログ記事に書いてあることにすごく近い気がする。うまくいくといいな :) (編集:HNはユニコードをフィルタリングするから、残念 :( )
編集:これに対してたくさんのダウンボートをもらってるけど、誰も私が間違ってる理由を言ってくれない。もし私が間違ってると思うなら、理由を教えてほしい。なんでそれが推奨される修正方法なのか理解できない。他の方法で解決できたと思うんだけど:1. ページをリサイズする時、どうやって割り当てられたかのフラグを残す。これは、サイズやアドレスフィールドの常に0ビットを使ってスペースを節約するためによく行われる。2. プールが連続したメモリの既知のサイズであるため、解放するメモリがその範囲内にあるかチェックする。3. サイズを不変にする。再割り当てしたいなら、やってみて、メモリマネージャーにその境界を処理させればいい。これらは機能性を維持するだけでなく、サイズの変更に対しても将来にわたって耐性があると思う。
あなたにアップボートしたのは、これらのアプローチに対する反応を知りたいからだよ。
俺はダウンボートしなかったけど、簡単な答えだと思う。修正は4行くらいだったし。結局、#1と#3はどちらもかなりの量のコードと複雑さを追加すると思うけど、それが堅牢性や明確さにどう繋がるのかはよくわからないな。修正内容から見ると、``` // 最初のノードが非標準のメモリサイズを持っている場合、再利用できない。 // これは、下のinitBufが基盤となるメモリの長さを変更してしまうからで、 // プールの外でメモリを解放するのが壊れてしまう。 // この場合、ノードを切り捨てるのが一番簡単だ。 ``` https://github.com/ghostty-org/ghostty/commit/17da13840dc71b... #3は、もっと広範な変更が必要みたい。サイズは今や実質的に不変だと思う(君のコメントを正しく理解していればだけど):非標準のページはサイズが変わらないから、サイズを変えようとせずに捨てられる。#2は面白いけど、MemoryPoolの実装が所有権をテストするのを簡単にするとは思えないな: https://github.com/ghostty-org/ghostty/blob/17da13840dc71ba3... アリーナバッファをチェックできるようにするにはいくつか変更が必要だし、そのチェックは単純な比較よりもずっと遅くなるだろう。
スクロールバックにサーキュラーバッファを使えばいいじゃん。どうせブロックを再利用するなら、なんでわざわざ使うの?それはさておき、いいまとめだね。
そういう風に始まったし、これは一般的なやり方だよ。理由の一つは、大きな事前割り当てや大きなコピーを避けるためなんだ。他にもいくつかのメモがlobstersにあるよ:https://lobste.rs/s/vlzg2m/finding_fixing_ghostty_s_largest_...
誰かが「Rustを選んでいればこんなことにはならなかった」と言うのを待ってる。
かなり待たされると思うよ。Rustは「リーク安全性」を構成的特性として明示的に持ってないからね。安全なRustプログラムはメモリリークを許可されてる。だって、メモリリーク自体は安全性の問題を引き起こさないから。メモリをリークするための標準的で非安全なAPIもあるし[1]。 (Rustがやってるのは、意図せずにメモリをリークするプログラムを作るのを難しくすること。可能ではあるけど、Rustで同じようなリークを表現するのが難しいとは限らない。)[1]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.l...
Ghosttyの開発をしばらく追ってきたけど、このプロジェクトには少し過剰設計がある気がする。でも、こういうバグのポストモーテムは、クラフトに恋してる人にとってはすごく価値があると思う。