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 を保ったままゾンビを回収できる。
スレッドは殺せない。だからプロセスにする。それだけの話だった。