Rust Workers の panic、もうインスタンスを道連れにしないよ!
やっほー、しぃちゃんだよ! 今日はちょっとマニアックだけど、とっても大事な信頼性のお話だよ。Rust で書いた Workers が panic しても、もう道連れにしなくなるんだって! わくわくしちゃうね。
Cloudflare Blog
なにが発表されたの?
Cloudflare の Blog が、Rust Workers の信頼性をぐっと高める仕組みを発表したの。テーマは「panic と abort からの回復」。Rust を WebAssembly (Wasm) に変換する土台になっている wasm-bindgen に手を入れて、panic が起きても WebAssembly インスタンスを壊さずに立て直せるようにしたんだって。
今までどうだったの?
これまで Rust Workers で panic が起きると、けっこう深刻だったの。wasm32-unknown-unknown ターゲットは既定で panic=abort になっていて、panic するといきなり unreachable 命令でトラップして、JavaScript 側に WebAssembly.RuntimeError として抜けちゃう。しかもインスタンスの状態が壊れて、元に戻せなくなっちゃうの。
一番こわいのは、1 つのリクエストの失敗が、同じインスタンスで動いている別のリクエストや、新しく来たリクエストにまで連鎖しちゃうこと。Cloudflare は最初、JavaScript の Proxy でラップして失敗後にモジュールを作り直す方法で対処していたんだけど、これだと再初期化のたびにメモリ上の状態が消えちゃうの。Durable Objects みたいに状態を持つワークロードにはつらいよね。
これで何が変わるの?
新しい仕組みでは、panic がちゃんと JavaScript の例外 (PanicError) として表に出てくるようになったの。だから catch して扱えるし、async な export なら返り値の Promise が PanicError で reject されるよ。
しかも Rust のデストラクタ(後片付け)が正しく走って、WebAssembly インスタンスはそのまま使い続けられるの! つまりメモリ上の状態を失わずに回復できるってこと。状態を持つ Durable Objects にとっては、特にうれしいポイントだね。
深く潜ってみよう
カギになったのは、2023 年に主要エンジンで広くサポートされた WebAssembly Exception Handling の仕組み。これを使って wasm32-unknown-unknown でも panic=unwind を実現したの。
ビルドはこんな感じで、標準ライブラリごと unwind 対応で組み直すよ:
RUSTFLAGS='-Cpanic=unwind' cargo build -Zbuild-std
unwind するとき、コンパイラは WebAssembly の try / catch ブロックを生成して、途中で panic してもデストラクタが走るようにするの。イメージはこんな感じ:
try
call imported_func
catch_all
call drop_b
call drop_a
rethrow
end
wasm-bindgen 側もいろいろ改修が必要だったんだって。Walrus っていう WebAssembly パーサに try / catch を理解させたり、export を extern "C-unwind" にして Rust と JavaScript の境界を越えて unwind できるようにしたり。境界で panic をキャッチして PanicError にして返す仕組みも入ったよ。詳しい仕組みは Catching Panics のガイド にまとまってるの。
それでも abort は残るの。メモリ不足 (OOM) みたいな abort は unwind できないから状態は回復できないんだけど、検知して次の処理への連鎖を防ぐことはできるようにしたよ。Exception.Tag を使って「回復できる panic」と「回復できない abort」を見分けて、初期化時に付けられる set_on_abort フックや、深く入り組んだ呼び出しのための再入ガードを用意したんだって。こっちは Handling Aborts のガイド に載ってるよ。
使う側としては、workers-rs 0.8.0 以降でビルド時に --panic-unwind フラグを付けると有効になるの(設定の手順はここ)。ただし今は nightly の実験的なターゲットが必要で、安定版に持っていく作業は進行中。unwind と相性の悪い参照をキャプチャするクロージャは、Closure::new_aborting を使うなどの工夫がいるよ。
まとめ
- Rust Workers の panic が、インスタンス全体を壊さず JavaScript の PanicError として回復できるようになったよ
- WebAssembly Exception Handling を使って
panic=unwindを実現、デストラクタも正しく走ってメモリ状態を維持できるの - abort(OOM など)は Exception.Tag で見分けて連鎖を防ぐ設計。
set_on_abortフックも追加されたよ - 使うには workers-rs 0.8.0 以降で
--panic-unwind。今は nightly 前提で、安定版化は進行中 - Rust で Workers や Durable Objects を書いていて、信頼性を突き詰めたい人にドンピシャな話だよ!