Skip to content
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

How to use Javy with asynchronous JavaScript code? (experimental_event_loop) #626

Closed
konradkoschel opened this issue Apr 8, 2024 · 5 comments
Labels
question Further information is requested

Comments

@konradkoschel
Copy link

What is your question?

Hi, I try to execute asynchronous JavaScript code using Javy. the simplified JavaScript code looks like this:

const input = JSON.parse(readInput()); // Read STDIN as described in README

(async () => {
  const result = await foo(input);

  writeOutput(result); // Write to STDOUT as described in README
})();

async function foo(params) {
  // ...
}

I am using a setup where I use the Javy Provider Module created via javy emit-provider -o javy-provider.wasm.

The JavaScript code itself is compiled via javy compile my.js -o out.wasm -d.

I execute everything in Wasmtime, and for sync code this works as expected. When using async code in the JavaScript code, I get the error Error while running JS: Adding tasks to the event queue is not supported.

I researched the origin of the issue and found out that in the file crates/core/src/execution.rs pending code (i.e. Promises) is only executed if the feature experimental_event_loop is set.

Therefore, I tried the following steps to fix my issue:

  1. I cloned the javy repository
  2. I installed all build prerequisites
  3. I ran cargo build -p javy-core --target wasm32-wasi -r --features experimental_event_loop
  4. I ran cargo build -p javy-cli -r --features experimental_event_loop
  5. I recompiled the JS with the same command as specified above
  6. I recreated the provider module with the same command as specified above
  7. I tested the execution again

Unfortunately, I ended up with the very same issue. Have I missed something?

Thanks for your help and clarification

P.S.: I am running on ARM-based MacOS, Rust 1.76.0, and built Javy on commit 8f4468c. The wasmtime(-wasi)/wasi-common version for the execution of the WASM is 15.0.0

@konradkoschel konradkoschel added the question Further information is requested label Apr 8, 2024
@jeffcharles
Copy link
Collaborator

I'm not able to reproduce the issue you ran into.

I created a module with this code:

(async () => {
  const result = await foo();

  console.log(result);
})();

async function foo(params) {
    return 'blah';
}

Then tested that compiling this with the default configuration outputs Error while running JS: Adding tasks to the event queue is not supported:

➜  javy git:(main) ✗ cargo build -p javy-cli -r      
   Compiling javy-cli v1.4.0 (/Users/jeffcharles/src/github.com/Shopify/javy/crates/cli)
    Finished release [optimized] target(s) in 1m 23s
➜  javy git:(main) ✗ target/release/javy compile -d index.js -o index.wasm
➜  javy git:(main) ✗ target/release/javy emit-provider -o provider.wasm                      
➜  javy git:(main) ✗ wasmtime run --preload javy_quickjs_provider_v1=provider.wasm index.wasm
Error while running JS: Adding tasks to the event queue is not supported
Error: failed to run main module `index.wasm`

Caused by:
    0: failed to invoke command default
    1: error while executing at wasm backtrace:
           0: 0xd5f17 - <unknown>!abort
           1: 0x283bd - <unknown>!std::sys::pal::wasi::abort_internal::h9244d020deeb80f3
           2: 0x3097 - <unknown>!std::process::abort::h9c7c1ac47130d7a9
           3: 0x28e9 - <unknown>!javy_quickjs_provider::execution::run_bytecode::h2795cc611b4a3c8a
           4: 0x506e - <unknown>!eval_bytecode
           5: 0xe0a90 - <unknown>!eval_bytecode.command_export
           6:   0xd0 - <unknown>!<wasm function 2>
       note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable may show more debugging information
    2: wasm trap: wasm `unreachable` instruction executed

Then I tested building with event loop support enabled:

➜  javy git:(main) ✗ cargo build -p javy-core --target wasm32-wasi -r --features=experimental_event_loop
   Compiling javy-core v0.2.0 (/Users/jeffcharles/src/github.com/Shopify/javy/crates/core)
    Finished release [optimized] target(s) in 1.11s
➜  javy git:(main) ✗ cargo build -p javy-cli -r                                              
   Compiling javy-cli v1.4.0 (/Users/jeffcharles/src/github.com/Shopify/javy/crates/cli)
    Finished release [optimized] target(s) in 1m 23s
➜  javy git:(main) ✗ target/release/javy emit-provider -o provider.wasm                      
➜  javy git:(main) ✗ wasmtime run --preload javy_quickjs_provider_v1=provider.wasm index.wasm
blah

Note that the experimental_event_loop feature for the CLI, at the present time, just changes which integration tests are run by cargo test, so you can enable it but you don't have to. You do need to recompile the CLI with a core module that has the experimental event loop feature enabled however. You also don't need to recompile the JS module.

Can you double-check you're still seeing the issue if you run the steps I've outlined above in the same order? If you are seeing the same issue, can you please include all commands you ran in the exact order you ran them including any outputs from those commands?

@konradkoschel
Copy link
Author

I tried your snippet and you are right. With this snippet, everything works as it should. However, I digged deeper to figure out why it doesn't work for my setup whereas it does for yours:

In the example that you provided, no actual async logic (such as IO or event loop activities) is performed. Most likely, your code is desugared under the hood to a Promise.resolve() call. I tested the following snippet:

(async () => {
  const result = await foo();

  console.log(result);
})();

async function foo(params) {
  await Promise.resolve("dummy");
  return 'blah';
}

This snippet worked and as expected, blah was outputed.

If actual async logic is involved, the issues come up. See this minimal example using setTimeout:

(async () => {
  console.log("start");
  const result = await foo();

  console.log(result);
})();

async function foo(params) {
  await (
    new Promise((resolve) => {
      setTimeout(() => {
        resolve();
      }, 1000);
    })
  );
  return 'blah';
}

Notice, I also added a log for "start" at the beginning of the async IIFE. When running this example, "start" is printed out, while "blah" is not.

For the execution, this time I used the wasmtime cli in the exact same way as you specified in your response.

@jeffcharles
Copy link
Collaborator

Ah! So you're not seeing the error message about adding tasks to the event queue not being supported. But rather you're expecting to see output but not seeing the output.

The script you provided won't execute successfully since Javy does not provide an implementation for setTimeout. Javy only provides ECMAScript APIs along with incomplete support for TextEncoder, TextDecoder, and a couple console methods. setTimeout is not an ECMAScript API. Though we'd be open to a contribution adding an implementation for it.

That said, there is a bug in that Javy should throw an error that an undefined function has been invoked.

Running

setTimeout(() => { console.log("hello") }, 1000);

for example results in

➜  javy git:(main) ✗ wasmtime run index.wasm                           
Error while running JS: Uncaught ReferenceError: 'setTimeout' is not defined
    at <anonymous> (function.mjs:1)

Error: failed to run main module `index.wasm`

Caused by:
    0: failed to invoke command default
    1: error while executing at wasm backtrace:
           0: 0x64894 - <unknown>!<wasm function 119>
           1: 0x738a8 - <unknown>!<wasm function 170>
           2: 0xbeaef - <unknown>!<wasm function 1042>
    2: wasm trap: wasm `unreachable` instruction executed

@jeffcharles
Copy link
Collaborator

jeffcharles commented Apr 9, 2024

Filed #627 to track the lack of error messages and traps when there's an error in processing tasks in the event loop top-level async functions that are immediately executed.

@konradkoschel
Copy link
Author

Yes, that the Error while running JS: Adding tasks to the event queue is not supported was still shown after I switched to the Javy Build with the experimental feature activated was a mistake on my setup – I was still using the original build.

Also, I wasn't aware that setTimeout is not supported at all (in fact I thought that this function was part of the ECMAScript specification). So thank you for the clarification and generally for your help!

That no error is thrown at all is therefore a bug as you said. Since this is captured in #627, I think my question is answered, so feel free to close the issue.

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

No branches or pull requests

2 participants