js-pdk icon indicating copy to clipboard operation
js-pdk copied to clipboard

fix: make sure we don't panic when an exception is thrown in an async context

Open mhmd-azeez opened this issue 7 months ago • 5 comments

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

mhmd-azeez avatar May 18 '25 19:05 mhmd-azeez

To be clear, normal exception were already being handled properly

mhmd-azeez avatar May 18 '25 19:05 mhmd-azeez

Tried to make the exception more helpful by using set_host_promise_rejection_tracker, but

  1. 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
  2. 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

mhmd-azeez avatar May 18 '25 20:05 mhmd-azeez

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.

zshipko avatar May 19 '25 22:05 zshipko

Getting an update here. should we put this on ice? re-approach it?

bhelx avatar Jul 11 '25 16:07 bhelx

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

mhmd-azeez avatar Jul 15 '25 14:07 mhmd-azeez