Skip to content

Commit e679d51

Browse files
committed
Block in dlopen until all threads have loaded the module
Fixes: #18345
1 parent 8944037 commit e679d51

File tree

12 files changed

+312
-103
lines changed

12 files changed

+312
-103
lines changed

emcc.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1634,7 +1634,12 @@ def setup_pthreads(target):
16341634
]
16351635

16361636
if settings.MAIN_MODULE:
1637-
settings.REQUIRED_EXPORTS += ['_emscripten_thread_sync_code', '__dl_seterr']
1637+
settings.REQUIRED_EXPORTS += [
1638+
'_emscripten_thread_sync_code',
1639+
'_emscripten_thread_sync_code_async',
1640+
'_emscripten_proxy_sync_code',
1641+
'__dl_seterr',
1642+
]
16381643

16391644
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
16401645
'$exitOnMainThread',

src/library_pthread.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,87 @@ var LibraryPThread = {
10691069
#endif
10701070
},
10711071

1072+
#if MAIN_MODULE
1073+
$promiseMap: "new Map();",
1074+
$nextPromiseId: 1,
1075+
1076+
// Create a new promise that can be resolved or rejected by passing
1077+
// a unique ID to emscripten_promise_resolve/emscripten_promise_reject
1078+
// TODO(sbc): Should be factor this out and make this API public?
1079+
$newNativePromise__deps: ['$promiseMap', '$nextPromiseId'],
1080+
$newNativePromise: function(func, args) {
1081+
return new Promise((resolve, reject) => {
1082+
var promiseId = nextPromiseId;
1083+
nextPromiseId += 1;
1084+
promiseMap.set(promiseId, {resolve, reject});
1085+
// Native promise function take promise ID as last argument
1086+
args.push(promiseId);
1087+
func.apply(null, args);
1088+
});
1089+
},
1090+
1091+
_emscripten_promise_resolve__deps: ['$promiseMap'],
1092+
_emscripten_promise_resolve__sig: 'vip',
1093+
_emscripten_promise_resolve: function(id, value) {
1094+
#if RUNTIME_DEBUG
1095+
err('emscripten_resolve_promise: ' + id);
1096+
#endif
1097+
assert(promiseMap.has(id));
1098+
promiseMap.get(id).resolve(value);
1099+
promiseMap.delete(id);
1100+
},
1101+
1102+
_emscripten_promise_reject__deps: ['$promiseMap'],
1103+
_emscripten_promise_reject__sig: 'vip',
1104+
_emscripten_promise_reject: function(id) {
1105+
#if RUNTIME_DEBUG
1106+
dbg('emscripten_promise_reject: ' + id);
1107+
#endif
1108+
assert(promiseMap.has(id));
1109+
promiseMap.get(id).reject();
1110+
},
1111+
1112+
// Called on the main thread to syncronize the code loaded on all threads.
1113+
// This work happens asyncronously. The `callback` is called once this work
1114+
// is completely, passing the ctx.
1115+
// TODO(sbc): Should make a new form of __proxy attribute for JS library
1116+
// function that run asyncroubly like but block the caller until they are
1117+
// done. Perhaps "async_with_ctx"?
1118+
_emscripten_sync_all_threads__sig: 'viii',
1119+
_emscripten_sync_all_threads__deps: ['_emscripten_proxy_sync_code', '$newNativePromise'],
1120+
_emscripten_sync_all_threads: function(caller, callback, ctx) {
1121+
#if PTHREADS_DEBUG
1122+
dbg("_emscripten_sync_all_threads caller=" + ptrToString(caller));
1123+
#endif
1124+
#if ASSERTIONS
1125+
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_sync_all_threads() can only ever be called from main thread');
1126+
#endif
1127+
1128+
let promises = [];
1129+
1130+
// This first promise resolves once the main thread has loaded all module
1131+
promises.push(newNativePromise(__emscripten_thread_sync_code_async, [0]));
1132+
1133+
// We then create a sequence of promises, one per thread, that resolve once
1134+
// each thread has performed its sync using _emscripten_proxy_sync_code.
1135+
for (const ptr of Object.keys(PThread.pthreads)) {
1136+
const pthread_ptr = Number(ptr);
1137+
if (pthread_ptr !== caller) {
1138+
promises.push(newNativePromise(__emscripten_proxy_sync_code, [pthread_ptr]));
1139+
}
1140+
}
1141+
1142+
// Once all promises are resolved then we we know all threads are in
1143+
// sync and we can call the callback.
1144+
Promise.all(promises).then(() => {
1145+
#if PTHREADS_DEBUG
1146+
dbg("_emscripten_sync_all_threads done: calling callback");
1147+
#endif
1148+
{{{ makeDynCall('vp', 'callback') }}}(ctx);
1149+
});
1150+
},
1151+
#endif
1152+
10721153
$executeNotifiedProxyingQueue: function(queue) {
10731154
// Set the notification state to processing.
10741155
Atomics.store(HEAP32, queue >> 2, {{{ cDefine('NOTIFICATION_RECEIVED') }}});

system/include/emscripten/threading.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,6 @@ int emscripten_pthread_attr_settransferredcanvases(pthread_attr_t *a, const char
286286
// blocking is not enabled, see ALLOW_BLOCKING_ON_MAIN_THREAD.
287287
void emscripten_check_blocking_allowed(void);
288288

289-
// Experimental API for syncing loaded code between pthreads.
290-
void _emscripten_thread_sync_code();
291-
292289
void _emscripten_yield();
293290

294291
#ifdef __cplusplus

0 commit comments

Comments
 (0)