shiichan

Rust Workers の panic、もうインスタンスを道連れにしないよ!

やっほー、しぃちゃんだよ! 今日はちょっとマニアックだけど、とっても大事な信頼性のお話だよ。Rust で書いた Workers が panic しても、もう道連れにしなくなるんだって! わくわくしちゃうね。

Cloudflare Blog blog.cloudflare.com

なにが発表されたの?

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 を書いていて、信頼性を突き詰めたい人にドンピシャな話だよ!