Skip to content

Problems encountered using asyncify and promises. #217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pxbos13 opened this issue Jan 14, 2025 · 1 comment
Open

Problems encountered using asyncify and promises. #217

pxbos13 opened this issue Jan 14, 2025 · 1 comment

Comments

@pxbos13
Copy link

pxbos13 commented Jan 14, 2025

Hello, thank you for providing this tool.

I'm having issues using asyncify and promises. The following code is an example of a usage scenario.

import variant from "@jitl/quickjs-wasmfile-release-asyncify"
import {
  newQuickJSAsyncWASMModuleFromVariant,
  QuickJSAsyncContext,
  QuickJSHandle,
} from "quickjs-emscripten-core"

async function init() {
  const qjsModule = await newQuickJSAsyncWASMModuleFromVariant(variant)
  const runtime = qjsModule.newRuntime()
  const ctx = runtime.newContext()

  defineObj(ctx)
  defineLogger(ctx)
  exposeEval(ctx)
}

init()
function sleep(ms: number) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(undefined)
    }, ms)
  })
}

function defineObj(ctx: QuickJSAsyncContext) {
  const _obj = ctx.newObject()
  const asyncHandle = ctx.newFunction("getAsync", function () {
    const _prom = ctx.newPromise()
    setTimeout(() => {
      _prom.resolve(ctx.newString(""))
    }, 10)
    _prom.settled.then(ctx.runtime.executePendingJobs)
    return _prom.handle
  })
  asyncHandle.consume((handle) => ctx.setProp(_obj, "getAsync", handle))

  ctx.defineProp(_obj, "name", {
    get: async function name() {
      await sleep(10)
      return ctx.newString("QuickJs")
    } as any,
  })
  ctx.setProp(ctx.global, "temp", _obj)
}

function defineLogger(ctx: QuickJSAsyncContext) {
  const logHandle = ctx.newFunction("log", function (...args: QuickJSHandle[]) {
    const nativeArgs = args.map(ctx.dump)
    console.log("[sandbox]", ...nativeArgs)
  })
  ctx.setProp(ctx.global, "log", logHandle)
}

function exposeEval(ctx: QuickJSAsyncContext) {
  ;(window as any)._eval = async function (script: string) {
    const res = await ctx.evalCodeAsync(script)
    ctx.runtime.executePendingJobs()
    console.log("result", res)
  }
}

Then when I use the asyncify method and promise in the sandbox, an error will be reported. The following example:

_eval(`
    async function test(i){
        log("start", i)
        await temp.getAsync();
        const name = temp.name;
        log(i, name);
    }
    for( let i = 0; i < 2; i ++){
        test(i)
    }
`)

Snipaste_2025-01-14_15-41-35

In another scenario, I changed it to await Promise.resolve(0).

_eval(`
    async function test(i){
        log("start", i)
        await Promise.resolve(0);
        const name = temp.name;
        log(i, name);
    }
    for( let i = 0; i < 2; i ++){
        test(i)
    }
`)

When _eval was executed for the first time, no error was reported; another error was reported for the second execution.

Snipaste_2025-01-14_15-49-59

Please help me how to deal with this problem. Many thanks.

@justjake
Copy link
Owner

The reason the first example fails is because your call to executePendingJobs is re-entering the execution of the WASM module while it is suspended waiting on the name.get native promise, and during the re-entered executePendingJobs callback, you are trying to suspend on the next call to name.get.

I’m not sure what the issue is in the second example. Can you try it with the DEBUG variant and enable source maps? That would allow us to see a stack trace of the C code where the violation occurs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants