Skip to content

Never run callbacks synchronously in emscripten_dlopen #24481

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
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/lib/libeventloop.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ LibraryJSEventLoop = {
$emClearImmediate_deps: ['$emSetImmediate'],
$emClearImmediate: undefined,

emscripten_queue_microtask__deps: ['$emSetImmediate', '$callUserCallback'],
emscripten_queue_microtask: (cb, userData) => {
{{{ runtimeKeepalivePush(); }}}
return queueMicrotask(() => {
{{{ runtimeKeepalivePop(); }}}
callUserCallback(() => {{{ makeDynCall('vp', 'cb') }}}(userData));
});
},

emscripten_set_immediate__deps: ['$emSetImmediate', '$callUserCallback'],
emscripten_set_immediate: (cb, userData) => {
{{{ runtimeKeepalivePush(); }}}
Expand Down
8 changes: 7 additions & 1 deletion src/lib/libpromise.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,23 @@ addToLibrary({
'emscripten_promise_destroy'],
emscripten_promise_resolve: (id, result, value) => {
#if RUNTIME_DEBUG
dbg(`emscripten_promise_resolve: ${id}`);
dbg(`emscripten_promise_resolve: ${id} -> ${value}`);
#endif
var info = promiseMap.get(id);
switch (result) {
case {{{ cDefs.EM_PROMISE_FULFILL }}}:
info.resolve(value);
return;
case {{{ cDefs.EM_PROMISE_MATCH }}}:
#if ASSERTIONS
assert(id != value, 'cannot resolve promise to itself')
#endif
info.resolve(getPromise(value));
return;
case {{{ cDefs.EM_PROMISE_MATCH_RELEASE }}}:
#if ASSERTIONS
assert(id != value, 'cannot resolve promise to itself')
#endif
info.resolve(getPromise(value));
_emscripten_promise_destroy(value);
return;
Expand Down
1 change: 1 addition & 0 deletions src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ sigs = {
emscripten_promise_race__sig: 'ppp',
emscripten_promise_resolve__sig: 'vpip',
emscripten_promise_then__sig: 'ppppp',
emscripten_queue_microtask__sig: 'ipp',
emscripten_random__sig: 'f',
emscripten_request_animation_frame__sig: 'ipp',
emscripten_request_animation_frame_loop__sig: 'vpp',
Expand Down
2 changes: 2 additions & 0 deletions system/include/emscripten/eventloop.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ void emscripten_set_immediate_loop(bool (*cb)(void *user_data), void *user_data)
int emscripten_set_interval(void (*cb)(void *user_data) __attribute__((nonnull)), double interval_ms, void *user_data);
void emscripten_clear_interval(int id);

int emscripten_queue_microtask(void (*cb)(void *user_data) __attribute__((nonnull)), void *user_data);

void emscripten_runtime_keepalive_push(void);
void emscripten_runtime_keepalive_pop(void);
bool emscripten_runtime_keepalive_check(void);
Expand Down
40 changes: 26 additions & 14 deletions system/lib/libc/dynlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#endif

struct async_data {
struct dso* dso;
em_dlopen_callback onsuccess;
em_arg_callback_func onerror;
void* user_data;
Expand Down Expand Up @@ -468,6 +469,7 @@ static void dlopen_onsuccess(struct dso* dso, void* user_data) {
dso,
dso->mem_addr,
dso->mem_size);
assert(dso == data->dso);
load_library_done(dso);
do_write_unlock();
data->onsuccess(data->user_data, dso);
Expand Down Expand Up @@ -601,37 +603,47 @@ void* dlopen(const char* file, int flags) {
return _dlopen(file, flags);
}

static void run_success_callback(void* user_data) {
struct async_data* data = (struct async_data*)user_data;
dlopen_onsuccess(data->dso, data);
}

static void run_error_callback(void* user_data) {
struct async_data* data = (struct async_data*)user_data;
dlopen_onerror(data->dso, data);
}

void emscripten_dlopen(const char* filename, int flags, void* user_data,
em_dlopen_callback onsuccess, em_arg_callback_func onerror) {
dbg("emscripten_dlopen: %s", filename);
if (!filename) {
onsuccess(user_data, head->dso);
return;
}

// Struct used to hold info on for the async callback
struct async_data* d = malloc(sizeof(struct async_data));
d->user_data = user_data;
d->onsuccess = onsuccess;
d->onerror = onerror;

do_write_lock();
char buf[2*NAME_MAX+2];
filename = find_dylib(buf, filename, sizeof buf);
struct dso* p = find_existing(filename);
if (p) {
onsuccess(user_data, p);
d->dso = find_existing(filename);
if (d->dso) {
emscripten_queue_microtask(run_success_callback, d);
return;
}
p = load_library_start(filename, flags);
if (!p) {
do_write_unlock();
onerror(user_data);
d->dso = load_library_start(filename, flags);
if (!d->dso) {
emscripten_queue_microtask(run_error_callback, d);
return;
}

// For async mode
struct async_data* d = malloc(sizeof(struct async_data));
d->user_data = user_data;
d->onsuccess = onsuccess;
d->onerror = onerror;

dbg("calling emscripten_dlopen_js %p", p);
// Unlock happens in dlopen_onsuccess/dlopen_onerror
_emscripten_dlopen_js(p, dlopen_onsuccess, dlopen_onerror, d);
_emscripten_dlopen_js(d->dso, dlopen_onsuccess, dlopen_onerror, d);
}

static void promise_onsuccess(void* user_data, void* handle) {
Expand Down
21 changes: 19 additions & 2 deletions test/other/test_dlopen_promise.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,25 @@
#include <emscripten/emscripten.h>
#include <emscripten/promise.h>

void load_side_module();

int load_count = 0;
bool in_callback = false;

em_promise_result_t on_fullfilled(void **result, void* data, void *handle) {
printf("onsuccess\n");
printf("onsuccess: %d\n", load_count++);
int* foo = (int*)dlsym(handle, "foo");
assert(foo);
printf("foo = %d\n", *foo);
assert(*foo == 42);

in_callback = true;
if (load_count < 2) {
// Load the same again, and make sure we don't re-enter ourselves.
load_side_module();
}
in_callback = false;

return EM_PROMISE_FULFILL;
}

Expand All @@ -20,11 +33,15 @@ em_promise_result_t on_rejected(void **result, void* data, void *value) {
return EM_PROMISE_FULFILL;
}

int main() {
void load_side_module() {
em_promise_t inner = emscripten_dlopen_promise("libside.so", RTLD_NOW);
em_promise_t outer = emscripten_promise_then(inner, on_fullfilled, on_rejected, NULL);
emscripten_promise_destroy(outer);
emscripten_promise_destroy(inner);
}

int main() {
load_side_module();
printf("returning from main\n");
return 0;
}
4 changes: 3 additions & 1 deletion test/other/test_dlopen_promise.out
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
returning from main
onsuccess
onsuccess: 0
foo = 42
onsuccess: 1
foo = 42