promise

1 posts

cloudflare

Making Rust Workers reliable: panic and abort recovery in wasm‑bindgen (opens in new tab)

Rust Workers historically treated Rust panics and aborts as fatal WebAssembly failures, potentially poisoning a Worker instance and causing unrelated requests to fail. Cloudflare’s latest work upstreamed into `wasm-bindgen` adds comprehensive recovery: `panic=unwind` preserves application state after recoverable panics, while abort handling ensures Rust code cannot run again after an unrecoverable abort. ## Initial Recovery Mitigations - Early Rust Workers used a custom panic handler to track failures and reinitialize the entire application before serving later requests. - JavaScript bindings were wrapped with Proxy-based indirection so every Rust entry point passed through recovery logic. - Generated bindings were modified to reinitialize the WebAssembly module after failures. - This approach shipped by default in `workers-rs` 0.6 and prevented persistent failure modes, but reinitialization could discard in-memory state. ## Panic Unwinding with WebAssembly Exception Handling - WebAssembly’s `wasm32-unknown-unknown` target traditionally defaults to `panic=abort`, turning panics into traps and `WebAssembly.RuntimeError` exceptions. - With WebAssembly Exception Handling support, Rust can be compiled using: ```bash RUSTFLAGS='-Cpanic=unwind' cargo build -Zbuild-std ``` - Unwinding allows Rust destructors to run, preserving state and cleaning up resources instead of terminating the entire instance. - `std::panic::catch_unwind` can translate a Rust panic into a recoverable `Result`. ## Changes to wasm-bindgen - The Walrus WebAssembly parser was updated to understand `try`/`catch` exception-handling instructions. - The descriptor interpreter was updated to evaluate code containing exception blocks. - Generated exports now catch Rust panics at the Rust–JavaScript boundary and expose them as `PanicError` exceptions. - Async exports reject their JavaScript promises with `PanicError`. - Exported functions use `extern "C-unwind"` so unwinding is explicitly permitted across the boundary. - A `MaybeUnwindSafe` trait checks `UnwindSafe` requirements only when compiling with `panic=unwind`. - For closures that cannot safely unwind, `Closure::new_aborting` provides an explicit alternative that terminates on panic rather than risking invalid state. ## Results of `panic=unwind` - Panics in exported Rust functions are caught by `wasm-bindgen`. - JavaScript receives a `PanicError`. - Async calls reject their promises instead of poisoning the Worker. - Rust destructors execute correctly. - The WebAssembly instance remains valid and reusable. - Stateful applications, including Durable Objects, can recover without losing all in-memory state. ## Abort Recovery - `panic=unwind` cannot handle aborts such as out-of-memory failures because aborts do not unwind. - The remaining recovery mechanism prevents Rust code from being re-entered after an abort, avoiding repeated execution in a corrupted WebAssembly state. - Together, unwinding and abort recovery prevent one failed request from poisoning sibling or future requests. The recommended approach is to use the latest `wasm-bindgen` and Rust Workers releases, enabling `panic=unwind` where state preservation matters while using explicit aborting closures when unwind safety cannot be guaranteed.