fix: make sure we don't panic when an exception is thrown in an async context
Fixes #134
What's going on?
In this code sample:
import { createHighlighter } from 'shiki'
export function main() {
try {
const code = 'const a = 1' // input code
const highlighter = createHighlighter({
themes: ['nord'],
langs: ['javascript'],
})
console.log('BAAM!')
//@ts-ignore - `codeToHtml` doesn't exist, `highlighter` is not awaited, so it's still a Promise
const html = highlighter.codeToHtml(code, {
lang: 'javascript',
theme: 'nord'
})
Host.outputString(html);
} catch (error) {
console.log('error: ', error)
throw error
}
}
highlighter is not awaited, so it's still a Promise so codeToHtml doesn't exist.
And it seems like quickjs can't deal with promise rejections well:
- https://github.com/DelSkayn/rquickjs/issues/421
- https://github.com/quickjs-ng/quickjs/issues/39
- https://github.com/DelSkayn/rquickjs/pull/422
- https://github.com/quickjs-ng/quickjs/pull/1038
I have made sure we handle this case, and we let the user know async is not supported in Extism functions (see issue_134 example):
➜ issue_134 git:(fix/issue_134) ✗ npm run build && extism call ../issue_134.wasm main --wasi --log-level info
> [email protected] build
> cross-env NODE_ENV=production node esbuild.js && cross-env ../../target/release/extism-js dist/index.js -i src/index.d.ts -o ../issue_134.wasm
2025/05/18 22:42:23 BAAM!
2025/05/18 22:42:23 error: TypeError: not a function
at main2 (eval_script:14216:31)
Error: Uncaught exception in async context. Async/Promise operations are not supported in this environment. Please use only synchronous code.
returned non-zero exit code: 4294967295
To be clear, normal exception were already being handled properly
Tried to make the exception more helpful by using set_host_promise_rejection_tracker, but
- The version they are exposing doesn't really work: https://github.com/DelSkayn/rquickjs/issues/421#issuecomment-2619540649, it's fixed here https://github.com/quickjs-ng/quickjs/pull/1038
- couldn't install rquickjs 0.9:
➜ js-pdk git:(main) ✗ make cli
cd crates/core \
&& cd src/prelude \
&& npm install \
&& npm run build \
&& npx -y -p typescript tsc src/index.ts --lib es2020 --declaration --emitDeclarationOnly --outDir dist \
&& cd ../.. \
&& cargo build --release --target=wasm32-wasip1 \
&& wasm-opt --enable-reference-types --enable-bulk-memory --strip -O3 ../../target/wasm32-wasip1/release/js_pdk_core.wasm -o ../../target/wasm32-wasip1/release/js_pdk_core.wasm \
&& cd -
up to date, audited 6 packages in 596ms
1 package is looking for funding
run `npm fund` for details
1 moderate severity vulnerability
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
> @extism/[email protected] build
> node esbuild.js && tsc
Compiling rquickjs-sys v0.9.0
Compiling js-pdk-core v1.5.1 (/home/mo/work/dylibso/js-pdk/crates/core)
error: failed to run custom build command for `rquickjs-sys v0.9.0`
Caused by:
process didn't exit successfully: `/home/mo/work/dylibso/js-pdk/target/release/build/rquickjs-sys-1078b1f618b07837/build-script-build` (exit status: 101)
--- stdout
cargo:rerun-if-changed=build.rs
cargo:rerun-if-env-changed=CARGO_FEATURE_BINDGEN
cargo:rerun-if-env-changed=CARGO_FEATURE_UPDATE_BINDINGS
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_BYTECODE
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_GC
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_GC_FREE
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_FREE
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_LEAKS
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_MEM
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_OBJECTS
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_ATOMS
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_SHAPES
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_MODULE_RESOLVE
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_PROMISE
cargo:rerun-if-env-changed=CARGO_FEATURE_DUMP_READ_OBJECT
SDK tar: "/home/mo/work/dylibso/js-pdk/target/wasm32-wasip1/release/build/rquickjs-sys-01c66f6d8f136e1e/out/wasi-sdk/wasi-sdk-24-0.tar.gz"
Downloading WASI SDK archive from https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-24/wasi-sdk-24.0-x86_64-linux.tar.gz to "/home/mo/work/dylibso/js-pdk/target/wasm32-wasip1/release/build/rquickjs-sys-01c66f6d8f136e1e/out/wasi-sdk/wasi-sdk-24-0.tar.gz"
curl output:
curl err: % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 113M 100 113M 0 0 864k 0 0:02:14 0:02:14 --:--:-- 422k
--- stderr
Debian clang version 14.0.6
Target: wasm32-unknown-wasip1
Thread model: posix
InstalledDir:
ignoring nonexistent directory "lib/clang/14.0.6/include"
ignoring nonexistent directory "/home/mo/work/dylibso/js-pdk/target/wasm32-wasip1/release/build/rquickjs-sys-01c66f6d8f136e1e/out/wasi-sdk/share/wasi-sysroot/local/include/wasm32-wasip1"
ignoring nonexistent directory "/home/mo/work/dylibso/js-pdk/target/wasm32-wasip1/release/build/rquickjs-sys-01c66f6d8f136e1e/out/wasi-sdk/share/wasi-sysroot/local/include"
ignoring nonexistent directory "/home/mo/work/dylibso/js-pdk/target/wasm32-wasip1/release/build/rquickjs-sys-01c66f6d8f136e1e/out/wasi-sdk/share/wasi-sysroot/include/wasm32-wasip1"
ignoring nonexistent directory "/home/mo/work/dylibso/js-pdk/target/wasm32-wasip1/release/build/rquickjs-sys-01c66f6d8f136e1e/out/wasi-sdk/share/wasi-sysroot/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/llvm-14/lib/clang/14.0.6/include
End of search list.
/home/mo/work/dylibso/js-pdk/target/wasm32-wasip1/release/build/rquickjs-sys-01c66f6d8f136e1e/out/quickjs.h:31:10: fatal error: 'stdio.h' file not found
thread 'main' panicked at /home/mo/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rquickjs-sys-0.9.0/build.rs:311:39:
Unable to generate bindings: ClangDiagnostic("/home/mo/work/dylibso/js-pdk/target/wasm32-wasip1/release/build/rquickjs-sys-01c66f6d8f136e1e/out/quickjs.h:31:10: fatal error: 'stdio.h' file not found\n")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
make: *** [Makefile:18: core] Error 101
something i noticed looking at this example, is that it runs correctly when I make the main function async:
export async function main() {
this leads me to believe that the error is actually related to calling async code in a non-async function or something lower level than the actual exception being raised.
the unreachable error also goes away if I leave the function signature the same, but remove the call returning a promise which kind of supports that idea.
i think we need to investigate this a little more, since async definitely does work, but there is still something weird going on.
Getting an update here. should we put this on ice? re-approach it?
i think @zshipko would be the best person to decide here, I am okay with both options. This seems deeper than I initially assumed. Would also be happy to help