Giter Club home page Giter Club logo

Comments (11)

justjake avatar justjake commented on June 8, 2024 1

@yar2001 I've started a PR to add an ASYNCIFY build variant and a new API surface for dealing with asyncified QuickJS interactions here: #54

I'm still figuring out the best way to offer both sync and async APIs, how to support existing code that expects everything to be sync, etc.

from quickjs-emscripten.

justjake avatar justjake commented on June 8, 2024

Yeah, I’ve thought about this before as well. You’re right to bring up ASYNCIFY, which is a possible path forward. We could do a second build of QuickJS with -O3 and an asyncify wrapper function around this function:

JSValue qts_quickjs_to_c_callback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_data) {

But, ASYNCIFY is a very big hammer for this problem, meant to work around C-level issues. We might be able to find a way around the issue in QuickJS’s C api.

from quickjs-emscripten.

justjake avatar justjake commented on June 8, 2024

I’ve checked, it seems impossible to use the C API to suspend the VM execution state. So, the options are ASYNCIFY, or do something strange: you can write your own suspense logic, because you control the script execution.

Whenever the user code would need to suspend to await a promise, throw the promise instead. Catch the promise at the top level, and await it. Once it’s ready, re-run the user code, but return the unwrapped promise result when the user code calls the async function again. Continue until you reach completion or hit a CPU or time budget.

For that idea to work, you need to ensure that your users’ code is as pure as possible, so that when you re-run it, it doesn’t diverge on subsequent runs, and so it doesn’t trigger effects multiple times.

from quickjs-emscripten.

yar2001 avatar yar2001 commented on June 8, 2024

Thanks for the advice! Actually, I have considered this method. However, user may use Math.rand or Date. Overriding it by static rand seed or other solution seems will solve the problem, but I think it is not general. I want to design an API for users of many levels and I can't make sure that this won't conflict with future plans.

I'm new to wasm, and after a whole day of exploration, I finally got what your "ASYNCIFY is a very big hammer" means. Perhaps I will try C++ with Embind. If I get anything good, I'll let you know.

from quickjs-emscripten.

yar2001 avatar yar2001 commented on June 8, 2024

I was unable to get an ideal way to achieve that. However, I found a way to execute async function by "polling". It's not elegant but it works.
Here is the prototype:

//async.cpp
#include <emscripten.h>
#include <emscripten/bind.h>
#include <map>
#include <iostream>

using namespace emscripten;

std::map<int, bool> fnWaitMap;
std::map<int, bool> fnRetMap;

void exeucteAsync(int fnId)
{
  float (*f)(int) = reinterpret_cast<float (*)(int)>(fnId);
  f(7);
  fnWaitMap[fnId] = false;
  while (!fnWaitMap[fnId])
  {
    emscripten_sleep(4);
  }
  EM_ASM_(Module.removeFunction($0), f);
  std::cout << "Output " << fnRetMap[fnId] << std::endl;
}

void asyncResolve(int fnId, int ret)
{
  fnWaitMap[fnId] = true;
  fnRetMap[fnId] = ret;
}

EMSCRIPTEN_BINDINGS(my_module)
{
  function("execute", &exeucteAsync);
  function("asyncResolve", &asyncResolve);
}

(it can also work in c, not limited to the cpp)

//async.try.js
const Module = require('./async.js');

function executeAsync(fn) {
  const fnId = Module.addFunction((parm) => {
    fn(parm).then((ret) => {
      Module.asyncResolve(fnId, ret);
    });
  }, 'fi');

  return Module.execute(fnId);
}

Module.onRuntimeInitialized = () => {
  executeAsync(async (a) => {
    await new Promise((resolve) => setTimeout(() => resolve(), 1000));
    return a ** 2;
  });
};

setInterval(() => {
  console.log('thread is not block!');
}, 1000);
emcc -o async.js --bind async.cpp -s ALLOW_TABLE_GROWTH -s EXPORTED_RUNTIME_METHODS='["cwrap", "addFunction", "removeFunction", "stringToUTF8", "lengthBytesUTF8"]' -O3 -s ASYNCIFY
node async.js

And the result should be:

image

The key point is emscripten_sleep. It can let wasm VM wait without blocking the thread.

from quickjs-emscripten.

justjake avatar justjake commented on June 8, 2024

The key point is emscripten_sleep. It can let wasm wait without block the thread.

true, but any function that calls down to emscripten_sleep is going to be suspended - it’s stack unwound and stored by ASYNCIFY. That means that even using polling, the compiler will need to ASYNCIFY all of quickjs, which might lead to some issues. So, I don’t see much advantage to using this polling method over a direct async call of your APIs.

After reading your issue the other day, I found someone else worked on ASYNCIFYing quickjs-emscripten in a fork and asked them about it on Twitter: https://twitter.com/louroboros/status/1481689244603072513?s=21

it seems like they originally tried using ASYNCIFY but encountered some challenges so they removed it in this commit. But, maybe you could continue their work from the parent commit? It looks like a good start: namuol@29c816b

from quickjs-emscripten.

yar2001 avatar yar2001 commented on June 8, 2024

That means that even using polling, the compiler will need to ASYNCIFY all of quickjs, which might lead to some issues.

Sorry, I don't get it. Won't ASYNCIFY all of quickjs is an expected behavior? Because from quickjs perspective, the thread of sync function is blocked until the async function in host releases it. Will there exist a solution to achieve ASYNCIFY without freezing all the quickjs? Do I miss something?
Or are you indicating that it will influence multiple QuickJSVm instances?
The reason I use polling method is because I fail to find a way to execute async function outside VM directly. All the pages of asyncify in manual is about asyncifying Web APIs. I'm not familiar with emscripten yet. But after your reply, now I know that I can directly call function outside the VM by EM_ASYNC_JS.

const Module = require('./async2');

async function waitForSecond() {
  await new Promise((resolve) => setTimeout(() => resolve(), 1000));
}

Module.waitForSecond = waitForSecond;

setInterval(() => {
  console.log('thread is not block!');
}, 1000);
#include <emscripten.h>
#include <emscripten/bind.h>

using namespace emscripten;

EM_ASYNC_JS(void, waitSecond, (),
            {
              out("before waiting");
              await Module.waitForSecond();
              out("after waiting");
            });

int main()
{
  waitSecond();
}

from quickjs-emscripten.

Macil avatar Macil commented on June 8, 2024

Besides using an ASYNCIFY build, a strategy that works now is to run quickjs-emscripten inside of a separate worker thread, have the worker thread and the main thread share a SharedArrayBuffer, and then at any time your code in the worker thread can communicate to the main thread through the SharedArrayBuffer and use Atomics.wait() to block the worker until the main thread responds. There's some discussion of the technique here: pyodide/pyodide#1219

from quickjs-emscripten.

justjake avatar justjake commented on June 8, 2024

I got the asyncified function API passing a test this morning in #54. There’s still a bit of work left on async module loading, though.

from quickjs-emscripten.

yar2001 avatar yar2001 commented on June 8, 2024

OMG thank you so much for your amazing work, this may be the first elegant solution in the entire community for asynchronous to synchronous.
I'm really sorry that I can't give you a hand. I'm busy preparing for my undergraduate internship interview recently, but my project and my users still need it very much. I will try this scheme when I have time later. Thanks again!

from quickjs-emscripten.

yar2001 avatar yar2001 commented on June 8, 2024

Nice, I migrated from vm2 to quickjs-emscripten, and it works great! The old temporary solution is to fetch all the data user codes needed which is really slow, and the new asyncify solution gives me a lot of room to optimize performance.

Besides, quickjs-emscripten has better memory and resource control, and be able to control many of the security vulnerabilities I know so far such as unceasing promise and thread blocking. (Surely don't need to care about prototype chain pollution, global scope leakage since the VM is absolutely isolated from the host)

There are also spillover effects, that I can use the sandbox in both browser and server. The sharing of code and the consistent code behavior greatly enhances the development and debugging efficiency. In the near future, I will develop an interactive teaching program for my users and it will be very useful for sandbox execution in the browser.

For anyone who is investigating the JS sandbox technology stack, quickjs-emscripten would be a good choice. I would recommend this framework to anyone around who needs it.

from quickjs-emscripten.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.