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:
    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.