Skip to content

Commit c606203

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

16 files changed

+475
-135
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: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,90 @@ 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+
#if RUNTIME_DEBUG
1085+
dbg('newNativePromise: ' + promiseId);
1086+
#endif
1087+
promiseMap.set(promiseId, {resolve, reject});
1088+
// Native promise function take promise ID as last argument
1089+
args.push(promiseId);
1090+
func.apply(null, args);
1091+
});
1092+
},
1093+
1094+
_emscripten_promise_resolve__deps: ['$promiseMap'],
1095+
_emscripten_promise_resolve__sig: 'vip',
1096+
_emscripten_promise_resolve: function(id, value) {
1097+
#if RUNTIME_DEBUG
1098+
err('emscripten_resolve_promise: ' + id);
1099+
#endif
1100+
assert(promiseMap.has(id));
1101+
promiseMap.get(id).resolve(value);
1102+
promiseMap.delete(id);
1103+
},
1104+
1105+
_emscripten_promise_reject__deps: ['$promiseMap'],
1106+
_emscripten_promise_reject__sig: 'vip',
1107+
_emscripten_promise_reject: function(id) {
1108+
#if RUNTIME_DEBUG
1109+
dbg('emscripten_promise_reject: ' + id);
1110+
#endif
1111+
assert(promiseMap.has(id));
1112+
promiseMap.get(id).reject();
1113+
},
1114+
1115+
// Called on the main thread to syncronize the code loaded on all threads.
1116+
// This work happens asyncronously. The `callback` is called once this work
1117+
// is completely, passing the ctx.
1118+
// TODO(sbc): Should make a new form of __proxy attribute for JS library
1119+
// function that run asyncroubly like but block the caller until they are
1120+
// done. Perhaps "async_with_ctx"?
1121+
_emscripten_sync_all_threads__sig: 'viii',
1122+
_emscripten_sync_all_threads__deps: ['_emscripten_proxy_sync_code', '$newNativePromise'],
1123+
_emscripten_sync_all_threads: function(caller, callback, ctx) {
1124+
#if PTHREADS_DEBUG
1125+
dbg("_emscripten_sync_all_threads caller=" + ptrToString(caller));
1126+
#endif
1127+
#if ASSERTIONS
1128+
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_sync_all_threads() can only ever be called from main thread');
1129+
#endif
1130+
1131+
let promises = [];
1132+
1133+
// This first promise resolves once the main thread has loaded all module
1134+
promises.push(newNativePromise(__emscripten_thread_sync_code_async, []));
1135+
1136+
// We then create a sequence of promises, one per thread, that resolve once
1137+
// each thread has performed its sync using _emscripten_proxy_sync_code.
1138+
for (const ptr of Object.keys(PThread.pthreads)) {
1139+
const pthread_ptr = Number(ptr);
1140+
if (pthread_ptr !== caller) {
1141+
promises.push(newNativePromise(__emscripten_proxy_sync_code, [pthread_ptr]));
1142+
}
1143+
}
1144+
1145+
// Once all promises are resolved then we we know all threads are in
1146+
// sync and we can call the callback.
1147+
Promise.all(promises).then(() => {
1148+
#if PTHREADS_DEBUG
1149+
dbg("_emscripten_sync_all_threads done: calling callback");
1150+
#endif
1151+
{{{ makeDynCall('vp', 'callback') }}}(ctx);
1152+
});
1153+
},
1154+
#endif
1155+
10721156
$executeNotifiedProxyingQueue: function(queue) {
10731157
// Set the notification state to processing.
10741158
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)