WADA-DEV(7) $ /ja/blog/ttymap-engine-restart-journey/

NAME

ttymap-engine-restart-journey

SYNOPSIS

自作のターミナル地球儀 ttymap で、ポリゴン三角分割ライブラリ earcut が病的な入力で無限ループに陥り、ゾンビスレッドが CPU を焼き続けるバグに当たった。なぜエンジンを再起動可能にしたか、そしてなぜそれがスレッドではなくプロセスでなければならないのか ── safe Rust では走り続けるスレッドを外から殺せない、という一点の話。

DESCRIPTION

ttymap は端末に braille で地球儀を出す自作ツールだ(前の記事)。そのエンジンを「動かしたまま再起動できる」ようにした。なぜ再起動が要るのか、そしてなぜそれがプロセスの再起動なのかだけ書く。

なぜ再起動できるようにしたか

長く起動しっぱなしの ttymap が、操作していないのに CPU を 200% 近く食っていた。犯人はポリゴン三角分割の earcut。病的な(自己交差した)ポリゴンを食わせると無限ループに入ることがある。

ttymap は 200ms のタイムアウトで「諦めて別のワーカーに差し替える」防御を入れてあるので UI は固まらない。が、諦めたワーカーは止まらない。earcut の中で回り続けたまま放置される。つまり病的ポリゴンを含むタイルをパンするたびに、永久に CPU を焼くスレッドが1本ずつ増えていく(#305)。

セッションも今見ている地点も失わずに、この溜まったゴミを回収したい。だから「エンジンだけ作り直す」操作が欲しくなった。

なぜスレッドでなくプロセスか

最初は素朴に「暴走スレッドを kill すればいい」と思った。できない。

safe Rust の std::thread には kill / cancel の API が無い。タイトループに yield 点もフラグチェックも無ければ、外から止める手段がゼロだ。earcut の三角分割は1個の閉じたループで、キャンセル用のフックを持たない。「earcut が自分で抜ける」までスレッドは生き続ける ── 真の無限ループなら永遠に。

確実に回収する一般的手段は1つしかない ── プロセスごと殺す。プロセスを kill すれば、中で何をしていようと OS が全スレッドを問答無用で回収する。

最初は全部を1つのプロセスでやっていた ── タイル取得もデコードも描画も earcut も、ttymap バイナリの中だった。この #305 が、エンジンを別プロセスに切り出す主因になった(#348)。最初は earcut だけを subprocess に隔離する案も試したが、エンジン内が half-thread / half-process の歪なモデルになる。なので earcut だけでなく エンジンを丸ごと別プロセスにした(外部からの制御チャネルやヘッドレス描画・複数フロントエンドという別の圧力も同じ方向を指していたが、引き金はこのスレッドリークだった)。

単一プロセスなら「エンジンを再起動」=「アプリごと再起動」で、ターミナルも景色も全部失う。エンジンが別プロセスなら、子だけ殺して生やし直せば、TUI を保ったままゾンビを回収できる。

スレッドは殺せない。だからプロセスにする。それだけの話だった。

TAGS

個人開発 · Rust · TUI · プロセス分離

SEE ALSO