諦める supervisor
綺麗な shutdown は、全 worker がいずれ終わることを前提にしていた。次の probe は 1 worker を意図的にハングさせた —— すると、決して届かない受信をブロックして待つ collector が、run 全体を道連れにハングした。supervisor に要るのは諦める権利: 締切付きの受信、N ミリ秒まで待って永遠に待つ代わりに None を返すもの。6 件中 1 件の stuck job のコストは、いまや帳簿の timeout 1 行であってプログラムではない。
前話は channel に終わり方を教えた。それがカバーするのは worker が仕事を終えて掃けていく場合 —— 協力的な終わりだ。この話は非協力的な方について: 終わりも失敗もせず、ただ永遠にかかる worker。 あらゆる分散システムとあらゆるスレッドプールがいずれこの worker に出会う。そしてその出会いが 明らかにするのは、言語が受信のどちら側を守ってきたかだ。
一つの stuck job
probe は worker pool を再利用して破壊工作した: 6 jobs、そして 3 番の job だけが 2 秒眠る。他は
全部 10 ミリ秒 —— 決して返らないネットワーク呼び出し、消えたディスク、エッジケースを踏んだ
ループの代役だ。collector のロジックは自明な channel_recv results の 6 回。5 つの結果は即座に
届く。6 つ目は決して届かず、collector はそれをブロックして待つ —— 永遠に。前話の機構は全て設計
どおりに動いている: jobs channel は綺麗に閉じ、5 workers は掃けて return し、join も成功する
だろう。だがプログラム全体は一つのブロッキング受信でハングし、最も遅いメンバーの人質になる。
channel を閉じてもこれは直せない —— job が stuck だとまだ誰も知らないので、誰も何を閉じるべきか
知らない。collector に欠けているのは情報ではない。諦める権利だ。
締切付きの受信
channel_recv_timeout は channel とミリ秒予算を取り、その長さまで値をブロック待ちし、
timeout で None を返す —— closed & drained でも None なので、時限なし版の shutdown 挙動も
包摂する。collector は 300 ミリ秒予算で 6 回試すループになる: Some が 5 回、None が 1 回、
そして run はハングの代わりに帳簿 —— results=5 timeouts=1 —— で終わる。C backend の実装は
教科書どおり、絶対締切に対する pthread_cond_timedwait。reference interpreter はミリ秒粒度の
poll だ。OCaml の標準ライブラリに時限付き条件待ちが無いからで、ソースの注記はそう率直に言って
いる。「終わり」問題のもう一片は、既に解けていたと判明した: supervisor が stuck worker に
気づいた後の自然な次の一手はバッチの残りをキャンセルすることだが —— それは第一話の
channel_close を jobs channel にかけるだけだ。新しい primitive は要らない。そこで自明な問いが
立った: 古典的な並行ツールキットには、この言語に無い有名な primitive がもう一つある ——
複数の channel を同時に待つことだ。次の話はそれを必要とするプログラムを探しに行き、
primitive より良いものを持って帰ってくる。