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
empterpreter async / emscripten_sleep() not working, no error #3307
Comments
Does building with assertions and/or safe heap show anything? Is this on latest incoming? Various bugs were fixed recently. |
nope, that's why I felt like hitting the brick wall and opening an issue… The steps to reproduce build with
I just updated to The only thing I have to offer is building a minimal test case for
I have no clue of your test infrastructure though… |
A single standalone C++ file for a testcase would be good. |
I'm not able to reproduce the problem in a minimal test case. I've tried reproducing my scenario, but here I actually get an error thrown. |
Ok, following your steps, I do get an abort. Not sure why you aren't seeing it - maybe change the The abort is because we use With that out of the way, I get an assertion failure on the stack being adjusted during the async operation. I suspect this is because exceptions are on the stack when the pause occurs, which we have issues with. Do you need exceptions here? |
uncertain, but possible. libsass uses exceptions internally, I have no idea what the internal state looks like at the time of the callback being run (although i have no idea why there would be an exception on the stack). You said there are known issues with exceptions. Are those planned to be tackled or ignored? I'm not exactly in a hurry to get this working, so I don't mind waiting… My smaller test case, that produced the abort error (that libsass didn't) failed because of unclear reasons. I assume that was the |
Actually, looking a little closer, it isn't due to exceptions. It's the usage of ccall. ccall assumes synchronous execution, you can write
and Do you need the return value? If not, we could have a version of ccall that is aware of async. However, it would still be confusing if one did
the |
Well, I need a return value. How I get it is not important. In my libsass wrapper I pass various pointers to ccall() that are then "filled" by the function. If that still works, I can pass back the actual "return value" via one of those pointers as well.
We need some way to get to the data. Does that mean I have to kick off the async operation in one ccall() and poll for success (reading a pointer or using another ccall() in a loop)? If ccall() can't do the asnyc thing, but a new function could help with the polling, sure! (also a note in Empterpreter docs?) |
Ok, the model I think will work best is now on incoming. You can do as async ccall, supplying a final argument to ccall To get a return value, you can can Is that useful? |
I'm a bit confused by how this is supposed to work. I'll try to explain what I understood I have to do in order to accomplish my goals (allow a sync C function to run async JS callbacks) in code: function runAsynFunction(done) {
// prepare flag that we will set in library.js once the c function is done
window._async_function_done = false;
// invoke the C function, make emscripten trigger the async behavior
var cleanupAsyncCcall = Module.ccall(
'async_function',
null,
['string'],
['some input string'],
{async: true}
);
// ccall() returns immediately, although it's actually still running
function isAsyncFunctionReturned() {
// check if the c function is executed
if (!window._async_function_done) {
// wait and repeat
return setTimeout(isAsyncFunctionReturned, 100);
}
// we're done, so cleanup and run the callback (resolve the promise, …)
cleanupAsyncCcall();
done(window._async_function_return_value);
}
isAsyncFunctionReturned();
} // defined in library.js
extern "C" {
void doAsyncCallback(char *input);
bool isAsyncCallbackDone();
void returnAsyncFunction(char *message);
}
// exported function
void async_function(char *input) {
// do some sync stuff
doAsyncCallback();
while (!isAsyncCallbackDone()) {
// wait, then recheck
emscripten_sleep(100);
}
returnAsyncFunction(strdup("passing back return value"));
} // library.js
var asyncCallbackDone = false;
// extern C to kick off async callback
function doAsyncCallback(inputPointer) {
// make sure isAsyncCallbackDone() "blocks" until we're ready
asyncCallbackDone = false;
// work the input
var input = Module.Pointer_stringify(inputPointer);
// kick off our async thing and wait
doSomethingAsync(input).then(function() {
// async operation completed, set flag to inform C
asyncCallbackDone = true;
})
}
// extern C to query state of async callback
function isAsyncCallbackDone() {
return Number(asyncCallbackDone);
}
// extern C to inform JS about function execution completion
function returnAsyncFunction(returnValuePointer) {
var returnValue = Module.Pointer_stringify(returnValuePointer);
window._async_function_done = true;
window._async_function_return_value = returnValue;
}
I don't think I understand what you're trying to say :/
It is useful on a level that allows me to solve the problem. If we're talking "simple, obvious and easy to use", I guess I still have a few ideas…
|
I'm not sure I followed that example. But I see that the need to manually clean up is confusing, and meanwhile I figured out a way to avoid that. So now the model is simpler. Here is the testcase in the test suite, to explain:
That C function prints, waits a second asynchronously, then prints some more Then it calls
The ccall will return when the async operation begins. It is still in the middle of executing |
I was trying to illustrate how the manual polling/waiting for function completion worked, but see how ridiculously convoluted that was. I now understand what that EM_ASM does. When looking at calling JS from C I focused on the library thing probably because I needed to pass strings, which EM_ASM* doesn't seem to support. I guess that var _stack = Module.Runtime.stackSave();
function finished() {
console.log('the async operation is done!');
Module.Runtime.stackRestore(_stack);
}
ccall('func', null, [], [], { async: true }); Thanks for this! any idea when this will be officially released? |
You can pass strings, but you will need to do a little manual work. A C string is just a pointer, to turn that into a JS string, call Yes, What memory/stack do you need to clean up? ccall will now clean its own stack usage up. Otherwise, if you have your own stack usage you perform before ccall, you can clean it up in that Not sure when this will reach a stable release. If the bots are stable early next week, perhaps. |
that I'm aware of. However don't see how I would pass that to EM_ASM, unless the following works? char *text = strdup("foo");
EM_ASM_INT({
var text = Module.Pointer_stringify($0);
console.log("message", text);
}, text);
I create a bunch of pointers using Update: confirming that this works perfectly fine! |
Yeah, that looks like it should work. |
I guess this issue can be closed now? |
Sure. |
totally forogt: THANK YOU! :) |
np, and thanks for filing - this isn't a much used feature yet, so it's great to get feedback and work out the issues. |
I just installed 1.30.5 manually and I'm able to confirm success! yay! :) |
Great! |
Hey,
I've been trying to get Empterpreter Async to work for sass.js. By performing the dynamic decision process I created a whitelist. Compiling with various debug flags works fine. But when running the built result in my browser I get garbage output, do not see the back from timeout log and don't get any error thrown from emscripten either.
The invocation chain is
Module.ccall
(JS) ->sass_compile_emscripten
(C) -> libsass C++ stuff ->sass_importer_emscripten
(C), the last function in turn invokes JS callbacks usingextern "C"
and--js-library
Since I did not change how data is passed, I expected everything to work as before, only with 1s delay because of the
emscripten_sleep(1000)
. What piece of the puzzle am I missing here?Steps to reproduce:
The text was updated successfully, but these errors were encountered: