notes mizzy.org

競合は観測では閉じない。順序づけでしか閉じない

確認済み 出典: peitho/docs/plans/2026-08-05-cdp-ordered-pdf-print.md / peitho/CLAUDE.md Pitfalls (Issues #402/#408、いずれも実測) 作成/ 更新

peithoのPDF書き出しは、Chromeの --print-to-pdf でページを印刷する。ところがページ側には非同期の後処理(グラデーションとbox-shadowをラスタライズする「平坦化」)が走っていて、Chromeはそれの完了を待たない。Chromeが「読み込み終わった」と判断した時点で印刷する。平坦化が間に合わなければ、壊れたPDFが黙って出る。

修正の試みが二段階あったのが要点になっている。

一段目 — 観測

「平坦化が走らなかった」ことをstderrの合図で検出する。

しかしこれでは「走ったが競合に負けた」を捕まえられない。合図が届くころにはPDFのバイト列はもうディスク上にある。

二段目 — 観測

出力されたPDFを検査して、平坦化の痕跡が無ければ失敗とする。

これも駄目だった。平坦化器は意図的に処理を飛ばす図形があり(痕跡が残らないのが正常)、SVGの内側は見えない。痕跡の有無から「平坦化が済んだか」を判定することは原理的にできない。

三段目 — 順序づけ

印刷を自分で駆動し、平坦化の完了を確認してから印刷を指示する。

競合そのものが存在しなくなる。

The only shape that closes the gap is one that orders rather than observes.

docs/plans/2026-08-05-cdp-ordered-pdf-print.md

採った形

--print-to-pdf を捨て、CDP(Chrome DevTools Protocol)で印刷を自分で駆動する。

  1. --remote-debugging-port=0 でChromeを起動する(印刷の指示はまだ出さない)
  2. Runtime.evaluatedata-peitho-pdf-flattened 属性が現れるまで待つ
  3. 現れてから Page.printToPDF を呼ぶ

属性は平坦化器の成功経路と最上位catchの両方で立つので、スクリプトが動きさえすれば必ず現れる。現れなければ期限切れで大きな音を立てて失敗する。そして--print-to-pdf への退避経路は用意しない — 静かな退避は競合を呼び戻すからである。

一般化

「AがBより先に終わっていること」を保証したいとき、取れる形は二つしかない。

保証の強さ
観測Bを走らせ、Aの完了の痕跡を後から探す痕跡とAの完了が同値のときだけ成立
順序づけAの完了を確認してからBを起動する構成上、競合が存在しない

観測が成り立つ条件は思ったより厳しい。痕跡の不在が「やっていない」と同値でなければならないが、ここでは「やったが対象が無かった」も痕跡ゼロを生む。この二つが区別できない時点で、観測は判定器になりえない。

そしてこれは、typestateで壊れた状態を書けなくするが型について言っていることの、時間についての版である。あちらは「壊れた状態を表す値を構成できなくする」。こちらは「壊れた順序を実行できなくする」。どちらも、後から検査するのではなく、そもそも到達不能にしている。

静かな失敗であることも同じ根

この不具合が二度も対処を要したのは、失敗が静かだったからでもある。壊れたPDFは開けるし、見た目も一見おかしくない。黙って捨てるのは、落とすことより悪いと同じ構図で、損失の小ささと発覚の遅さが釣り合っていない

同じリポジトリに、これと双子の実測がある。Linuxのheadless Chromeで image.decode()解決も拒否もせず永遠に止まるという事象で、拒否されないので .catch() が発火せず、止まったこと自体が静かだった。ここでも手当ては同じ方向 — 期限を切って大きな音で失敗させる、退避経路は作らない。

#並行性 #設計原則 #peitho