diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index b4c2b96ac..bf73c7304 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -17,7 +17,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: dtolnay/rust-toolchain@1.83.0 + - uses: dtolnay/rust-toolchain@1.87.0 - uses: actions/checkout@v4 with: submodules: true @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: dtolnay/rust-toolchain@1.83.0 + - uses: dtolnay/rust-toolchain@1.87.0 - uses: actions/checkout@v4 with: @@ -88,7 +88,7 @@ jobs: needs: worker-build runs-on: ubuntu-latest steps: - - uses: dtolnay/rust-toolchain@1.83.0 + - uses: dtolnay/rust-toolchain@1.87.0 - uses: actions/checkout@v4 with: submodules: true @@ -127,7 +127,7 @@ jobs: name: Formatter runs-on: ubuntu-latest steps: - - uses: dtolnay/rust-toolchain@1.83.0 + - uses: dtolnay/rust-toolchain@1.87.0 with: components: rustfmt, clippy @@ -164,7 +164,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: dtolnay/rust-toolchain@1.83.0 + - uses: dtolnay/rust-toolchain@1.87.0 - uses: actions/checkout@v4 with: submodules: true @@ -220,3 +220,45 @@ jobs: - name: Run tests (http) run: chomp test-http + + test-panic-unwind: + name: Test (panic-unwind) + needs: worker-build + runs-on: ubuntu-latest + + steps: + - uses: dtolnay/rust-toolchain@nightly + with: + components: rust-src + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Chomp + uses: guybedford/chomp-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/download-artifact@v4 + with: + name: worker-build + path: ./target/debug + + - name: Make worker-build executable + run: chmod +x ./target/debug/worker-build + + - name: Cache Rust dependencies + uses: actions/cache@v4 + with: + path: ~/.cargo/registry ~/.cargo/git target + key: ${{ runner.os }}-cargo-nightly-${{ hashFiles('**/Cargo.lock') }} + restore-keys: ${{ runner.os }}-cargo-nightly- + + - name: Install npm dependencies + run: npm ci + + - name: Install wasm32 target + run: rustup target add wasm32-unknown-unknown + + - name: Run tests (panic-unwind) + run: chomp test-panic-unwind diff --git a/.gitmodules b/.gitmodules index 1f6decbe9..c0cb169f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "wasm-bindgen"] path = wasm-bindgen url = https://github.com/wasm-bindgen/wasm-bindgen +[submodule "wasm-streams"] + path = wasm-streams + url = git@github.com:guybedford/wasm-streams.git diff --git a/Cargo.lock b/Cargo.lock index d5a4bf230..e4372f752 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -322,7 +322,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -333,9 +333,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -575,7 +575,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-web", - "worker 0.7.3", + "worker 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -626,7 +626,7 @@ name = "digest-stream-on-workers" version = "0.1.0" dependencies = [ "hex", - "worker 0.7.3", + "worker 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -750,26 +750,25 @@ version = "0.1.0" dependencies = [ "serde", "serde_json", - "worker 0.7.3", + "worker 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "flate2" @@ -1281,8 +1280,6 @@ dependencies = [ [[package]] name = "js-sys" version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1293,7 +1290,7 @@ name = "kv-on-workers" version = "0.1.0" dependencies = [ "serde_json", - "worker 0.7.3", + "worker 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1739,7 +1736,7 @@ name = "queue-on-workers" version = "0.1.0" dependencies = [ "serde", - "worker 0.7.3", + "worker 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1893,7 +1890,7 @@ version = "0.1.0" dependencies = [ "serde", "tokio", - "worker 0.7.3", + "worker 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1963,18 +1960,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "ring", "rustls-pki-types", @@ -2311,11 +2308,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2331,9 +2328,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -2722,7 +2719,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "utf-8", ] @@ -2739,7 +2736,7 @@ dependencies = [ "log", "rand", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "utf-8", ] @@ -2983,8 +2980,6 @@ dependencies = [ [[package]] name = "wasm-bindgen" version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -2996,8 +2991,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-cli-support" version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be60cf36510aa4702ce189517229c3091f4a322a5ec2665a7737ea5956c2b3a" dependencies = [ "anyhow", "base64", @@ -3014,8 +3007,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" version = "0.4.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", "futures-util", @@ -3028,8 +3019,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3038,8 +3027,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -3051,8 +3038,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -3060,8 +3045,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-test" version = "0.3.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45649196a53b0b7a15101d845d44d2dda7374fc1b5b5e2bbf58b7577ff4b346d" dependencies = [ "async-trait", "cast", @@ -3082,8 +3065,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" version = "0.3.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f579cdd0123ac74b94e1a4a72bd963cf30ebac343f2df347da0b8df24cdebed2" dependencies = [ "proc-macro2", "quote", @@ -3093,8 +3074,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-shared" version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8145dd1593bf0fb137dbfa85b8be79ec560a447298955877804640e40c2d6ea" [[package]] name = "wasm-encoder" @@ -3119,6 +3098,19 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.205.0" @@ -3146,8 +3138,6 @@ dependencies = [ [[package]] name = "web-sys" version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -3555,13 +3545,13 @@ dependencies = [ [[package]] name = "worker" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c448438c7bb70830daa14a7871fa368f6ce275c90811fe659232fd0f18a7e4b3" +version = "0.7.4" dependencies = [ "async-trait", + "axum", "bytes", "chrono", + "chrono-tz", "futures-channel", "futures-util", "http", @@ -3574,24 +3564,26 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-postgres", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", + "wasm-bindgen-test", + "wasm-streams 0.5.0", "web-sys", - "worker-macros 0.7.3", - "worker-sys 0.7.3", + "worker-macros 0.7.4", + "worker-sys 0.7.4", ] [[package]] name = "worker" version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "244647fd7673893058f91f22a0eabd0f45bb50298e995688cb0c4b9837081b19" dependencies = [ "async-trait", - "axum", "bytes", "chrono", - "chrono-tz", "futures-channel", "futures-util", "http", @@ -3604,15 +3596,13 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", - "tokio-postgres", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", - "wasm-streams", + "wasm-streams 0.4.2", "web-sys", - "worker-macros 0.7.4", - "worker-sys 0.7.4", + "worker-macros 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", + "worker-sys 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3663,9 +3653,7 @@ dependencies = [ [[package]] name = "worker-macros" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6493a8c1e5da7b6915e644483b6be480efa464ae44a26e91ace29dedd111b917" +version = "0.7.4" dependencies = [ "async-trait", "proc-macro2", @@ -3674,12 +3662,14 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", - "worker-sys 0.7.3", + "worker-sys 0.7.4", ] [[package]] name = "worker-macros" version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac7e73ffb164183b57bb67d3efb881681fcd93ef5515ba32a4d022c4a6acc2ce" dependencies = [ "async-trait", "proc-macro2", @@ -3688,7 +3678,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", - "worker-sys 0.7.4", + "worker-sys 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3726,9 +3716,7 @@ dependencies = [ [[package]] name = "worker-sys" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ddb03eeeea05c7ee2950af42686cc3b19e0f2f7767214cdec7edbe1aa5e1254" +version = "0.7.4" dependencies = [ "cfg-if", "js-sys", @@ -3739,6 +3727,8 @@ dependencies = [ [[package]] name = "worker-sys" version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2b96254fcaa9229fd82d886f04be99c4ee8e59c8d80438724aa70039dca838" dependencies = [ "cfg-if", "js-sys", @@ -3910,7 +3900,7 @@ dependencies = [ "memchr", "pbkdf2", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "xz2", "zeroize", @@ -3920,9 +3910,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index ec63e4e02..4763782ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,12 +33,13 @@ serde-wasm-bindgen = "0.6.5" syn = "2.0.17" proc-macro2 = "1.0.60" quote = "1.0.28" -wasm-bindgen = { version = "0.2.106" } -wasm-bindgen-cli-support = { version = "0.2.106" } -wasm-bindgen-futures = { version = "0.4.56" } -wasm-bindgen-macro-support = { version = "0.2.106" } -wasm-bindgen-shared = { version = "0.2.106" } -wasm-bindgen-test = { version = "0.3.58" } +wasm-bindgen = { version = "0.2.108" } +wasm-bindgen-cli-support = { version = "0.2.108" } +wasm-bindgen-futures = { version = "0.4.58" } +wasm-bindgen-macro-support = { version = "0.2.108" } +wasm-bindgen-shared = { version = "0.2.108" } +wasm-bindgen-test = { version = "0.3.48" } +wasm-streams = { version = "0.5.0" } web-sys = { version = "0.3.85", features = [ "AbortController", "AbortSignal", @@ -99,3 +100,12 @@ opt-level = "z" # These are local patches we use to test against local wasm bindgen # We always align on the exact stable wasm bindgen version for releases +[patch.crates-io] +js-sys = { version = "0.3.85", path = './wasm-bindgen/crates/js-sys' } +wasm-bindgen = { version = "0.2.108", path = './wasm-bindgen' } +wasm-bindgen-cli-support = { version = "0.2.108", path = "./wasm-bindgen/crates/cli-support" } +wasm-bindgen-futures = { version = "0.4.57", path = './wasm-bindgen/crates/futures' } +wasm-bindgen-macro-support = { version = "0.2.108", path = "./wasm-bindgen/crates/macro-support" } +wasm-bindgen-shared = { version = "0.2.108", path = "./wasm-bindgen/crates/shared" } +wasm-bindgen-test = { version = "0.3.58", path = "./wasm-bindgen/crates/test" } +web-sys = { version = "0.3.85", path = './wasm-bindgen/crates/web-sys' } diff --git a/chompfile.toml b/chompfile.toml index 2737a47ae..3e7ab61cc 100644 --- a/chompfile.toml +++ b/chompfile.toml @@ -59,6 +59,17 @@ run = ''' npx vitest run --testTimeout 25000 ''' +[[task]] +name = 'test-panic-unwind' +dep = 'build' +cwd = 'test' +env.NO_MINIFY = '1' +env.WASM_BINDGEN_BIN = '../wasm-bindgen/target/debug/wasm-bindgen' +run = ''' +../target/debug/worker-build --dev --panic-unwind +npx vitest run --testTimeout 25000 +''' + [[task]] name = 'test-mem' cwd = 'test' diff --git a/package.json b/package.json index 27fad2a05..c0d2f67c8 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "build": "cd wasm-bindgen && cargo build -p wasm-bindgen-cli --bin wasm-bindgen && cd .. && cargo build -p worker-build", "test": "cd test && NO_MINIFY=1 WASM_BINDGEN_BIN=../wasm-bindgen/target/debug/wasm-bindgen ../target/debug/worker-build --dev && NODE_OPTIONS='--experimental-vm-modules' npx vitest run", "test-http": "cd test && NO_MINIFY=1 WASM_BINDGEN_BIN=../wasm-bindgen/target/debug/wasm-bindgen ../target/debug/worker-build --release --features http && NODE_OPTIONS='--experimental-vm-modules' npx vitest run", + "test-panic-unwind": "cd test && NO_MINIFY=1 WASM_BINDGEN_BIN=../wasm-bindgen/target/debug/wasm-bindgen ../target/debug/worker-build --dev --panic-unwind && NODE_OPTIONS='--experimental-vm-modules' npx vitest run", "test-mem": "cd test && npx wrangler dev --enable-containers=false", "lint": "cargo clippy --features d1,queue --all-targets --workspace -- -D warnings", "lint:fix": "cargo fmt && cargo clippy --features d1,queue --all-targets --workspace --fix -- -D warnings" diff --git a/test/tests/panic.spec.ts b/test/tests/panic.spec.ts index 2556052c5..c6a553547 100644 --- a/test/tests/panic.spec.ts +++ b/test/tests/panic.spec.ts @@ -5,125 +5,215 @@ describe("Panic Hook with WASM Reinitialization", () => { // These tests are explicitly run sequentially with a longer timeout // to ensure they fully run the reinitialization lifecycle. test("panic recovery tests", async () => { - // basic panic recovery - { - await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - const resp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - expect(await resp.text()).toContain("unstored_count: 2"); + // First, detect which panic mode we're running in by checking the error message + const detectResp = await mf.dispatchFetch(`${mfUrl}test-panic`); + const detectText = await detectResp.text(); + + // panic=unwind mode returns "PanicError:" in the response + // panic=abort mode returns "Workers runtime canceled" + const isPanicUnwind = detectText.includes("PanicError:"); + + if (isPanicUnwind) { + // ===== PANIC=UNWIND MODE TESTS ===== + // In this mode, panics are caught and converted to JS errors. + // The Worker continues without reinitialization. + + // Test 1: Basic panic recovery - counter should NOT reset after panic + // We use a unique durable object ID to get fresh state + { + const uniqueId = `UNWIND_${Date.now()}_${Math.random()}`; + + // Call the durable object twice to establish a counter + const resp1 = await mf.dispatchFetch(`${mfUrl}durable/${uniqueId}`); + const text1 = await resp1.text(); + // Extract the unstored_count from the response + const match1 = text1.match(/unstored_count: (\d+)/); + const count1 = match1 ? parseInt(match1[1]) : 0; + + const resp2 = await mf.dispatchFetch(`${mfUrl}durable/${uniqueId}`); + const text2 = await resp2.text(); + const match2 = text2.match(/unstored_count: (\d+)/); + const count2 = match2 ? parseInt(match2[1]) : 0; + expect(count2).toBe(count1 + 1); + + // Now trigger a panic + const panicResp = await mf.dispatchFetch(`${mfUrl}test-panic`); + expect(panicResp.status).toBe(500); + const panicText = await panicResp.text(); + expect(panicText).toContain("PanicError:"); + expect(panicText).toContain("Intentional panic"); + + // Counter should continue from where it was (not reset) + const resp3 = await mf.dispatchFetch(`${mfUrl}durable/${uniqueId}`); + const text3 = await resp3.text(); + const match3 = text3.match(/unstored_count: (\d+)/); + const count3 = match3 ? parseInt(match3[1]) : 0; + expect(count3).toBe(count2 + 1); + } - const panicResp = await mf.dispatchFetch(`${mfUrl}test-panic`); - expect(panicResp.status).toBe(500); + // Test 2: Multiple panics don't affect subsequent requests + { + const uniqueId = `UNWIND2_${Date.now()}_${Math.random()}`; + + const resp1 = await mf.dispatchFetch(`${mfUrl}durable/${uniqueId}`); + const match1 = (await resp1.text()).match(/unstored_count: (\d+)/); + const count1 = match1 ? parseInt(match1[1]) : 0; + + // Trigger multiple panics + for (let i = 0; i < 3; i++) { + const panicResp = await mf.dispatchFetch(`${mfUrl}test-panic`); + expect(panicResp.status).toBe(500); + expect(await panicResp.text()).toContain("PanicError:"); + } - const panicText = await panicResp.text(); - expect(panicText).toContain("Workers runtime canceled"); + // Counter should continue (not reset) + const resp2 = await mf.dispatchFetch(`${mfUrl}durable/${uniqueId}`); + const match2 = (await resp2.text()).match(/unstored_count: (\d+)/); + const count2 = match2 ? parseInt(match2[1]) : 0; + expect(count2).toBe(count1 + 1); + } - const normalResp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - expect(await normalResp.text()).toContain("unstored_count: 1"); - } + // Test 3: Concurrent requests with one panicking + { + const requests = [ + mf.dispatchFetch(`${mfUrl}test-panic`), + mf.dispatchFetch(`${mfUrl}durable/hello`), + mf.dispatchFetch(`${mfUrl}durable/hello`), + ]; + + const responses = await Promise.all(requests); + + // First should be 500 (panic), others should succeed + expect(responses[0].status).toBe(500); + expect(await responses[0].text()).toContain("PanicError:"); + expect(responses[1].status).toBe(200); + expect(responses[2].status).toBe(200); + } - // multiple requests after panic all succeed - { - const panicResp = await mf.dispatchFetch(`${mfUrl}test-panic`); - expect(panicResp.status).toBe(500); + } else { + // ===== PANIC=ABORT MODE TESTS (default) ===== + // In this mode, panics cause "Workers runtime canceled" and WASM reinitializes. - const requests = [ - mf.dispatchFetch(`${mfUrl}durable/hello`), - mf.dispatchFetch(`${mfUrl}durable/hello`), - mf.dispatchFetch(`${mfUrl}durable/hello`), - ]; + // basic panic recovery + { + await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + const resp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + expect(await resp.text()).toContain("unstored_count: 2"); - const responses = await Promise.all(requests); + const panicResp = await mf.dispatchFetch(`${mfUrl}test-panic`); + expect(panicResp.status).toBe(500); - for (let i = 0; i < responses.length; i++) { - const text = await responses[i].text(); - expect(responses[i].status).toBe(200); - expect(text).toContain("Hello from my-durable-object!"); - } - } + const panicText = await panicResp.text(); + expect(panicText).toContain("Workers runtime canceled"); - // simultaneous requests during panic handling - { - const simultaneousRequests = [ - mf.dispatchFetch(`${mfUrl}test-panic`), // This will panic - mf.dispatchFetch(`${mfUrl}durable/hello`), // This should succeed after reinitialization - mf.dispatchFetch(`${mfUrl}durable/hello`), - ]; - - const responses = await Promise.all(simultaneousRequests); - - // should always have one error and one ok - let foundErrors = 0; - for (const response of responses) { - if (response.status === 500) { - expect(foundErrors).toBeLessThan(2); - foundErrors++; - } else { - expect(response.status).toBe(200); - } + const normalResp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + expect(await normalResp.text()).toContain("unstored_count: 1"); } - expect(foundErrors).toBeGreaterThan(0); - } - // worker continues to function normally after multiple panics - { - for (let cycle = 1; cycle <= 3; cycle++) { + // multiple requests after panic all succeed + { const panicResp = await mf.dispatchFetch(`${mfUrl}test-panic`); expect(panicResp.status).toBe(500); - const recoveryResp = await mf.dispatchFetch(`${mfUrl}durable/hello`); - expect(recoveryResp.status).toBe(200); + const requests = [ + mf.dispatchFetch(`${mfUrl}durable/hello`), + mf.dispatchFetch(`${mfUrl}durable/hello`), + mf.dispatchFetch(`${mfUrl}durable/hello`), + ]; + + const responses = await Promise.all(requests); + + for (let i = 0; i < responses.length; i++) { + const text = await responses[i].text(); + expect(responses[i].status).toBe(200); + expect(text).toContain("Hello from my-durable-object!"); + } } - } - // explicit abort() recovery test - { - await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - const resp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - expect(await resp.text()).toContain("unstored_count:"); + // simultaneous requests during panic handling + { + const simultaneousRequests = [ + mf.dispatchFetch(`${mfUrl}test-panic`), // This will panic + mf.dispatchFetch(`${mfUrl}durable/hello`), // This should succeed after reinitialization + mf.dispatchFetch(`${mfUrl}durable/hello`), + ]; + + const responses = await Promise.all(simultaneousRequests); + + // should always have one error and one ok + let foundErrors = 0; + for (const response of responses) { + if (response.status === 500) { + expect(foundErrors).toBeLessThan(2); + foundErrors++; + } else { + expect(response.status).toBe(200); + } + } + expect(foundErrors).toBeGreaterThan(0); + } - const abortResp = await mf.dispatchFetch(`${mfUrl}test-abort`); - expect(abortResp.status).toBe(500); + // worker continues to function normally after multiple panics + { + for (let cycle = 1; cycle <= 3; cycle++) { + const panicResp = await mf.dispatchFetch(`${mfUrl}test-panic`); + expect(panicResp.status).toBe(500); - const abortText = await abortResp.text(); - expect(abortText).toContain("Workers runtime canceled"); + const recoveryResp = await mf.dispatchFetch(`${mfUrl}durable/hello`); + expect(recoveryResp.status).toBe(200); + } + } - const normalResp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - expect(await normalResp.text()).toContain("unstored_count: 1"); - } + // explicit abort() recovery test + { + await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + const resp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + expect(await resp.text()).toContain("unstored_count:"); - // out of memory recovery test - { - await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - const resp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - expect(await resp.text()).toContain("unstored_count:"); + const abortResp = await mf.dispatchFetch(`${mfUrl}test-abort`); + expect(abortResp.status).toBe(500); - const oomResp = await mf.dispatchFetch(`${mfUrl}test-oom`); - expect(oomResp.status).toBe(500); + const abortText = await abortResp.text(); + expect(abortText).toContain("Workers runtime canceled"); - const oomText = await oomResp.text(); - expect(oomText).toContain("Workers runtime canceled"); + const normalResp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + expect(await normalResp.text()).toContain("unstored_count: 1"); + } - const normalResp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - expect(await normalResp.text()).toContain("unstored_count: 1"); - } + // JS error recovery test + // TODO: figure out how to achieve this one. Hard part is global error handler + // will need to detect JS errors, not just WebAssembly.RuntimeError, which + // may over-classify. + // { + // await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + // const resp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + // expect(await resp.text()).toContain("unstored_count:"); + + // const jsErrorResp = await mf.dispatchFetch(`${mfUrl}test-js-error`); + // expect(jsErrorResp.status).toBe(500); + + // const jsErrorText = await jsErrorResp.text(); + // expect(jsErrorText).toContain("Workers runtime canceled"); - // JS error recovery test - // TODO: figure out how to achieve this one. Hard part is global error handler - // will need to detect JS errors, not just WebAssembly.RuntimeError, which - // may over-classify. - // { - // await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - // const resp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - // expect(await resp.text()).toContain("unstored_count:"); + // const normalResp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + // expect(await normalResp.text()).toContain("unstored_count: 1"); + // } - // const jsErrorResp = await mf.dispatchFetch(`${mfUrl}test-js-error`); - // expect(jsErrorResp.status).toBe(500); + // out of memory recovery test + { + await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + const resp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + expect(await resp.text()).toContain("unstored_count:"); - // const jsErrorText = await jsErrorResp.text(); - // expect(jsErrorText).toContain("Workers runtime canceled"); + const oomResp = await mf.dispatchFetch(`${mfUrl}test-oom`); + expect(oomResp.status).toBe(500); - // const normalResp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); - // expect(await normalResp.text()).toContain("unstored_count: 1"); - // } + const oomText = await oomResp.text(); + expect(oomText).toContain("Workers runtime canceled"); + + const normalResp = await mf.dispatchFetch(`${mfUrl}durable/COUNTER`); + expect(await normalResp.text()).toContain("unstored_count: 1"); + } + } }, 20_000); }); diff --git a/worker-build/src/build/mod.rs b/worker-build/src/build/mod.rs index aa3a0e5fb..83b5048a7 100644 --- a/worker-build/src/build/mod.rs +++ b/worker-build/src/build/mod.rs @@ -48,6 +48,7 @@ pub struct Build { pub extra_args: Vec, pub extra_options: Vec, pub wasm_bindgen_version: Option, + pub panic_unwind: bool, } /// What sort of output we're going to be generating and flags we're invoking @@ -158,6 +159,12 @@ pub struct BuildOptions { #[clap(long, hide = true)] /// Pass-through for --no-panic-recovery pub no_panic_recovery: bool, + + #[clap(long = "panic-unwind")] + /// Enable panic=unwind support. This uses nightly Rust and rebuilds std + /// with panic=unwind, allowing panics to be caught and converted to + /// JavaScript errors instead of aborting the Worker. + pub panic_unwind: bool, } type BuildStep = fn(&mut Build) -> Result<()>; @@ -221,6 +228,7 @@ impl Build { extra_args: Vec::new(), extra_options: build_opts.extra_options, wasm_bindgen_version: None, + panic_unwind: build_opts.panic_unwind, }) } @@ -332,7 +340,12 @@ impl Build { fn step_build_wasm(&mut self) -> Result<()> { info!("Building wasm..."); - target::cargo_build_wasm(&self.crate_path, self.profile.clone(), &self.extra_options)?; + target::cargo_build_wasm( + &self.crate_path, + self.profile.clone(), + &self.extra_options, + self.panic_unwind, + )?; info!( "wasm built at {:#?}.", diff --git a/worker-build/src/build/target.rs b/worker-build/src/build/target.rs index f7d531399..3ba2eb98f 100644 --- a/worker-build/src/build/target.rs +++ b/worker-build/src/build/target.rs @@ -217,11 +217,22 @@ pub fn cargo_build_wasm( path: &Path, profile: BuildProfile, extra_options: &[String], + panic_unwind: bool, ) -> Result<()> { - let msg = format!("{}Compiling to Wasm...", emoji::CYCLONE); + let msg = if panic_unwind { + format!("{}Compiling to Wasm (with panic=unwind)...", emoji::CYCLONE) + } else { + format!("{}Compiling to Wasm...", emoji::CYCLONE) + }; PBAR.info(&msg); let mut cmd = Command::new("cargo"); + + // When panic_unwind is enabled, use nightly toolchain + if panic_unwind { + cmd.arg("+nightly"); + } + cmd.current_dir(path).arg("build").arg("--lib"); if PBAR.quiet() { @@ -251,6 +262,20 @@ pub fn cargo_build_wasm( cmd.arg("--target").arg("wasm32-unknown-unknown"); + // When panic_unwind is enabled, we need to rebuild std with panic=unwind support + if panic_unwind { + cmd.arg("-Z").arg("build-std=panic_abort,std"); + + // Get existing RUSTFLAGS and append panic=unwind + let existing_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default(); + let new_rustflags = if existing_rustflags.is_empty() { + "-Cpanic=unwind".to_string() + } else { + format!("{} -Cpanic=unwind", existing_rustflags) + }; + cmd.env("RUSTFLAGS", new_rustflags); + } + // The `cargo` command is executed inside the directory at `path`, so relative paths set via extra options won't work. // To remedy the situation, all detected paths are converted to absolute paths. let mut handle_path = false; diff --git a/worker-build/src/js/shim.js b/worker-build/src/js/shim.js index 8518cd3f5..a3e27b44a 100644 --- a/worker-build/src/js/shim.js +++ b/worker-build/src/js/shim.js @@ -9,7 +9,7 @@ function registerPanicHook() { exports.setPanicHook(function (message) { const panicError = new Error("Rust panic: " + message); console.error('Critical', panicError); - criticalError = true; + $PANIC_CRITICAL_ERROR }); } diff --git a/worker-build/src/main.rs b/worker-build/src/main.rs index a20805dc4..597a463f1 100644 --- a/worker-build/src/main.rs +++ b/worker-build/src/main.rs @@ -107,7 +107,16 @@ pub fn main() -> Result<()> { } if module_target { - let shim = SHIM_FILE.replace("$HANDLERS", &generate_handlers(&out_dir)?); + let shim = SHIM_FILE + .replace("$HANDLERS", &generate_handlers(&out_dir)?) + .replace( + "$PANIC_CRITICAL_ERROR", + if builder.panic_unwind { + "" + } else { + "criticalError = true;" + }, + ); fs::write(output_path(&out_dir, "shim.js"), shim)?; add_export_wrappers(&out_dir)?; diff --git a/worker-macros/src/durable_object.rs b/worker-macros/src/durable_object.rs index 62fdd0e37..41b6a3f52 100644 --- a/worker-macros/src/durable_object.rs +++ b/worker-macros/src/durable_object.rs @@ -47,12 +47,12 @@ mod bindgen_methods { // to the durable object escape into a static-lifetime future. let static_self: &'static Self = unsafe { &*(self as *const _) }; - ::worker::wasm_bindgen_futures::future_to_promise(async move { + ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::fetch(static_self, req.into()).await .map(::worker::worker_sys::web_sys::Response::from) .map(::worker::wasm_bindgen::JsValue::from) .map_err(::worker::wasm_bindgen::JsValue::from) - }) + })) } } } @@ -67,12 +67,12 @@ mod bindgen_methods { // to the durable object escape into a static-lifetime future. let static_self: &'static Self = unsafe { &*(self as *const _) }; - ::worker::wasm_bindgen_futures::future_to_promise(async move { + ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::alarm(static_self).await .map(::worker::worker_sys::web_sys::Response::from) .map(::worker::wasm_bindgen::JsValue::from) .map_err(::worker::wasm_bindgen::JsValue::from) - }) + })) } } } @@ -98,11 +98,11 @@ mod bindgen_methods { // to the durable object escape into a static-lifetime future. let static_self: &'static Self = unsafe { &*(self as *const _) }; - ::worker::wasm_bindgen_futures::future_to_promise(async move { + ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::websocket_message(static_self, ws.into(), message).await .map(|_| ::worker::wasm_bindgen::JsValue::NULL) .map_err(::worker::wasm_bindgen::JsValue::from) - }) + })) } #[wasm_bindgen(js_name = webSocketClose, wasm_bindgen=::worker::wasm_bindgen)] @@ -119,11 +119,11 @@ mod bindgen_methods { // to the durable object escape into a static-lifetime future. let static_self: &'static Self = unsafe { &*(self as *const _) }; - ::worker::wasm_bindgen_futures::future_to_promise(async move { + ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::websocket_close(static_self, ws.into(), code, reason, was_clean).await .map(|_| ::worker::wasm_bindgen::JsValue::NULL) .map_err(::worker::wasm_bindgen::JsValue::from) - }) + })) } #[wasm_bindgen(js_name = webSocketError, wasm_bindgen=::worker::wasm_bindgen)] @@ -138,11 +138,11 @@ mod bindgen_methods { // to the durable object escape into a static-lifetime future. let static_self: &'static Self = unsafe { &*(self as *const _) }; - ::worker::wasm_bindgen_futures::future_to_promise(async move { + ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { ::websocket_error(static_self, ws.into(), error.into()).await .map(|_| ::worker::wasm_bindgen::JsValue::NULL) .map_err(::worker::wasm_bindgen::JsValue::from) - }) + })) } } } diff --git a/worker-macros/src/event.rs b/worker-macros/src/event.rs index 07d7f22c5..f7c9374d8 100644 --- a/worker-macros/src/event.rs +++ b/worker-macros/src/event.rs @@ -57,42 +57,47 @@ pub fn expand_macro(attr: TokenStream, item: TokenStream) -> TokenStream { }; // create a new "main" function that takes the worker_sys::Request, and calls the - // original attributed function, passing in a converted worker::Request + // original attributed function, passing in a converted worker::Request. + // We use a synchronous wrapper that returns a Promise via future_to_promise + // with AssertUnwindSafe to support panic=unwind. let wrapper_fn = quote! { - pub async fn #wrapper_fn_ident( + pub fn #wrapper_fn_ident( req: ::worker::worker_sys::web_sys::Request, env: ::worker::Env, ctx: ::worker::worker_sys::Context - ) -> ::worker::worker_sys::web_sys::Response { - let ctx = worker::Context::new(ctx); - match ::worker::FromRequest::from_raw(req) { - Ok(req) => { - let result = #input_fn_ident(req, env, ctx).await; - // get the worker::Result by calling the original fn - match result { - Ok(raw_res) => { - match ::worker::IntoResponse::into_raw(raw_res) { - Ok(res) => res, - Err(err) => { - let e: Box = err.into(); - ::worker::console_error!("Error converting response: {}", &e); - #error_handling + ) -> ::worker::js_sys::Promise { + ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { + let ctx = worker::Context::new(ctx); + let response: ::worker::worker_sys::web_sys::Response = match ::worker::FromRequest::from_raw(req) { + Ok(req) => { + let result = #input_fn_ident(req, env, ctx).await; + // get the worker::Result by calling the original fn + match result { + Ok(raw_res) => { + match ::worker::IntoResponse::into_raw(raw_res) { + Ok(res) => res, + Err(err) => { + let e: Box = err.into(); + ::worker::console_error!("Error converting response: {}", &e); + #error_handling + } } + }, + Err(err) => { + let e: Box = err.into(); + ::worker::console_error!("{}", &e); + #error_handling } - }, - Err(err) => { - let e: Box = err.into(); - ::worker::console_error!("{}", &e); - #error_handling } + }, + Err(err) => { + let e: Box = err.into(); + ::worker::console_error!("Error converting request: {}", &e); + #error_handling } - }, - Err(err) => { - let e: Box = err.into(); - ::worker::console_error!("Error converting request: {}", &e); - #error_handling - } - } + }; + Ok(::worker::wasm_bindgen::JsValue::from(response)) + })) } }; let wasm_bindgen_code = @@ -121,10 +126,15 @@ pub fn expand_macro(attr: TokenStream, item: TokenStream) -> TokenStream { // rename the original attributed fn input_fn.sig.ident = input_fn_ident.clone(); + // Use a synchronous wrapper that returns a Promise via future_to_promise + // with AssertUnwindSafe to support panic=unwind. let wrapper_fn = quote! { - pub async fn #wrapper_fn_ident(event: ::worker::worker_sys::ScheduledEvent, env: ::worker::Env, ctx: ::worker::worker_sys::ScheduleContext) { - // call the original fn - #input_fn_ident(::worker::ScheduledEvent::from(event), env, ::worker::ScheduleContext::from(ctx)).await + pub fn #wrapper_fn_ident(event: ::worker::worker_sys::ScheduledEvent, env: ::worker::Env, ctx: ::worker::worker_sys::ScheduleContext) -> ::worker::js_sys::Promise { + ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { + // call the original fn + #input_fn_ident(::worker::ScheduledEvent::from(event), env, ::worker::ScheduleContext::from(ctx)).await; + Ok(::worker::wasm_bindgen::JsValue::UNDEFINED) + })) } }; let wasm_bindgen_code = @@ -154,17 +164,22 @@ pub fn expand_macro(attr: TokenStream, item: TokenStream) -> TokenStream { // rename the original attributed fn input_fn.sig.ident = input_fn_ident.clone(); + // Use a synchronous wrapper that returns a Promise via future_to_promise + // with AssertUnwindSafe to support panic=unwind. let wrapper_fn = quote! { - pub async fn #wrapper_fn_ident(event: ::worker::worker_sys::MessageBatch, env: ::worker::Env, ctx: ::worker::worker_sys::Context) { - // call the original fn - let ctx = worker::Context::new(ctx); - match #input_fn_ident(::worker::MessageBatch::from(event), env, ctx).await { - Ok(()) => {}, - Err(e) => { - ::worker::console_log!("{}", &e); - panic!("{}", e); + pub fn #wrapper_fn_ident(event: ::worker::worker_sys::MessageBatch, env: ::worker::Env, ctx: ::worker::worker_sys::Context) -> ::worker::js_sys::Promise { + ::worker::wasm_bindgen_futures::future_to_promise(::std::panic::AssertUnwindSafe(async move { + // call the original fn + let ctx = worker::Context::new(ctx); + match #input_fn_ident(::worker::MessageBatch::from(event), env, ctx).await { + Ok(()) => {}, + Err(e) => { + ::worker::console_log!("{}", &e); + panic!("{}", e); + } } - } + Ok(::worker::wasm_bindgen::JsValue::UNDEFINED) + })) } }; let wasm_bindgen_code = diff --git a/worker/Cargo.toml b/worker/Cargo.toml index 56d29b1a4..2de23f3b2 100644 --- a/worker/Cargo.toml +++ b/worker/Cargo.toml @@ -34,7 +34,7 @@ tokio = { version = "1.28", default-features = false } url = "2.4.0" serde-wasm-bindgen.workspace = true serde_urlencoded = "0.7" -wasm-streams = "0.4" +wasm-streams.workspace = true worker-macros.workspace = true worker-sys.workspace = true chrono-tz = { version = "0.10.3", optional = true, default-features = false } diff --git a/worker/src/context.rs b/worker/src/context.rs index f84cfe3c4..30671c666 100644 --- a/worker/src/context.rs +++ b/worker/src/context.rs @@ -1,4 +1,5 @@ use std::future::Future; +use std::panic::AssertUnwindSafe; use crate::worker_sys::Context as JsContext; use crate::Result; @@ -36,10 +37,10 @@ impl Context { F: Future + 'static, { self.inner - .wait_until(&future_to_promise(async { + .wait_until(&future_to_promise(AssertUnwindSafe(async { future.await; Ok(JsValue::UNDEFINED) - })) + }))) .unwrap() } diff --git a/worker/src/durable.rs b/worker/src/durable.rs index b5605a1cd..f9b86984c 100644 --- a/worker/src/durable.rs +++ b/worker/src/durable.rs @@ -10,7 +10,7 @@ //! [Learn more](https://developers.cloudflare.com/workers/learning/using-durable-objects) about //! using Durable Objects. -use std::{fmt::Display, ops::Deref, time::Duration}; +use std::{fmt::Display, ops::Deref, panic::AssertUnwindSafe, time::Duration}; use crate::{ container::Container, @@ -265,10 +265,10 @@ impl State { F: Future + 'static, { self.inner - .wait_until(&future_to_promise(async { + .wait_until(&future_to_promise(AssertUnwindSafe(async { future.await; Ok(JsValue::UNDEFINED) - })) + }))) .unwrap() } @@ -551,12 +551,12 @@ impl Storage { { let inner: Box js_sys::Promise> = Box::new(move |t: DurableObjectTransaction| -> js_sys::Promise { - future_to_promise(async move { + future_to_promise(AssertUnwindSafe(async move { closure(Transaction { inner: t }) .await .map_err(JsValue::from) .map(|_| JsValue::NULL) - }) + })) }); let clos = wasm_bindgen::closure::Closure::once(inner); JsFuture::from(self.inner.transaction(&clos)?) diff --git a/worker/src/schedule.rs b/worker/src/schedule.rs index 830024719..28c737cfe 100644 --- a/worker/src/schedule.rs +++ b/worker/src/schedule.rs @@ -1,4 +1,5 @@ use std::future::Future; +use std::panic::AssertUnwindSafe; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; use worker_sys::{ScheduleContext as EdgeScheduleContext, ScheduledEvent as EdgeScheduledEvent}; @@ -55,10 +56,10 @@ impl ScheduleContext { T: Future + 'static, { self.edge - .wait_until(future_to_promise(async { + .wait_until(future_to_promise(AssertUnwindSafe(async { handler.await; Ok(JsValue::null()) - })) + }))) .unwrap() } }