Skip to content

Commit 2b9b728

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

File tree

14 files changed

+466
-145
lines changed

14 files changed

+466
-145
lines changed

emcc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1645,7 +1645,13 @@ def setup_pthreads(target):
16451645
]
16461646

16471647
if settings.MAIN_MODULE:
1648-
settings.REQUIRED_EXPORTS += ['_emscripten_thread_sync_code', '__dl_seterr']
1648+
settings.REQUIRED_EXPORTS += [
1649+
'_emscripten_dlsync_self',
1650+
'_emscripten_dlsync_self_async',
1651+
'_emscripten_proxy_dlsync',
1652+
'_emscripten_proxy_dlsync_async',
1653+
'__dl_seterr',
1654+
]
16491655

16501656
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += [
16511657
'$exitOnMainThread',

src/library_pthread.js

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ var LibraryPThread = {
3131
$PThread__deps: ['_emscripten_thread_init',
3232
'$killThread',
3333
'$cancelThread', '$cleanupThread', '$zeroMemory',
34+
#if MAIN_MODULE
35+
'$markAsZombie',
36+
#endif
3437
'$spawnThread',
3538
'_emscripten_thread_free_data',
3639
'exit',
@@ -55,6 +58,12 @@ var LibraryPThread = {
5558
// the reverse mapping, each worker has a `pthread_ptr` when its running a
5659
// pthread.
5760
pthreads: {},
61+
#if MAIN_MODULE
62+
// Zombie threads are threads that have finished running but we not yet
63+
// joined.
64+
zombieThreads: {},
65+
outstandingPromises: {},
66+
#endif
5867
#if PTHREADS_DEBUG
5968
nextWorkerID: 1,
6069
debugInit: function() {
@@ -267,6 +276,10 @@ var LibraryPThread = {
267276
spawnThread(d);
268277
} else if (cmd === 'cleanupThread') {
269278
cleanupThread(d['thread']);
279+
#if MAIN_MODULE
280+
} else if (cmd === 'markAsZombie') {
281+
markAsZombie(d['thread']);
282+
#endif
270283
} else if (cmd === 'killThread') {
271284
killThread(d['thread']);
272285
} else if (cmd === 'cancelThread') {
@@ -538,11 +551,20 @@ var LibraryPThread = {
538551
},
539552

540553
$cleanupThread: function(pthread_ptr) {
554+
#if PTHREADS_DEBUG
555+
dbg('cleanupThread: ' + ptrToString(pthread_ptr))
556+
#endif
541557
#if ASSERTIONS
542558
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! cleanupThread() can only ever be called from main application thread!');
543559
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cleanupThread!');
544560
#endif
545561
var worker = PThread.pthreads[pthread_ptr];
562+
#if MAIN_MODULE
563+
delete PThread.zombieThreads[pthread_ptr];
564+
if (pthread_ptr in PThread.outstandingPromises) {
565+
PThread.outstandingPromises[pthread_ptr].resolve();
566+
}
567+
#endif
546568
assert(worker);
547569
PThread.returnWorkerToPool(worker);
548570
},
@@ -1044,7 +1066,7 @@ var LibraryPThread = {
10441066
// Before we call the thread entry point, make sure any shared libraries
10451067
// have been loaded on this there. Otherwise our table migth be not be
10461068
// in sync and might not contain the function pointer `ptr` at all.
1047-
__emscripten_thread_sync_code();
1069+
__emscripten_dlsync_self();
10481070
#endif
10491071
// pthread entry points are always of signature 'void *ThreadMain(void *arg)'
10501072
// Native codebases sometimes spawn threads with other thread entry point
@@ -1073,6 +1095,90 @@ var LibraryPThread = {
10731095
#endif
10741096
},
10751097

1098+
#if MAIN_MODULE
1099+
_emscripten_thread_exit_joinable: function(thread) {
1100+
// Called when a thread exits and is joinable. This puts the thread
1101+
// into zombie state where it can't run anymore work but cannot yet
1102+
// be cleaned up.
1103+
if (!ENVIRONMENT_IS_PTHREAD) markAsZombie(thread);
1104+
else postMessage({ 'cmd': 'markAsZombie', 'thread': thread });
1105+
},
1106+
1107+
$markAsZombie: function(pthread_ptr) {
1108+
#if PTHREADS_DEBUG
1109+
dbg('markAsZombie: ' + ptrToString(pthread_ptr));
1110+
#endif
1111+
PThread.zombieThreads[pthread_ptr] = true;
1112+
if (pthread_ptr in PThread.outstandingPromises) {
1113+
PThread.outstandingPromises[pthread_ptr].resolve();
1114+
}
1115+
},
1116+
1117+
// Asynchronous version dlsync_threads. Always run on the main thread.
1118+
// This work happens asynchronously. The `callback` is called once this work
1119+
// is completed, passing the ctx.
1120+
// TODO(sbc): Should we make a new form of __proxy attribute for JS library
1121+
// function that run asynchronously like but blocks the caller until they are
1122+
// done. Perhaps "sync_with_ctx"?
1123+
_emscripten_dlsync_threads_async__sig: 'viii',
1124+
_emscripten_dlsync_threads_async__deps: ['_emscripten_proxy_dlsync_async', '$newNativePromise'],
1125+
_emscripten_dlsync_threads_async: function(caller, callback, ctx) {
1126+
#if PTHREADS_DEBUG
1127+
dbg("_emscripten_dlsync_threads_async caller=" + ptrToString(caller));
1128+
#endif
1129+
#if ASSERTIONS
1130+
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads_async() can only ever be called from main thread');
1131+
#endif
1132+
1133+
const promises = [];
1134+
assert(Object.keys(PThread.outstandingPromises).length === 0);
1135+
1136+
// This first promise resolves once the main thread has loaded all modules.
1137+
promises.push(newNativePromise(__emscripten_dlsync_self_async, null).promise);
1138+
1139+
// We then create a sequence of promises, one per thread, that resolve once
1140+
// each thread has performed its sync using _emscripten_proxy_dlsync.
1141+
// Any new threads that are created after this call will automaticaly be
1142+
// in sync because we call `__emscripten_dlsync_self` in
1143+
// invokeEntryPoint before the threads entry point is called.
1144+
for (const ptr of Object.keys(PThread.pthreads)) {
1145+
const pthread_ptr = Number(ptr);
1146+
if (pthread_ptr !== caller && !(pthread_ptr in PThread.zombieThreads)) {
1147+
var p = newNativePromise(__emscripten_proxy_dlsync_async, pthread_ptr);
1148+
PThread.outstandingPromises[pthread_ptr] = p;
1149+
promises.push(p.promise);
1150+
}
1151+
}
1152+
1153+
#if PTHREADS_DEBUG
1154+
dbg('_emscripten_dlsync_threads_async: waiting on ' + promises.length + ' promises');
1155+
#endif
1156+
// Once all promises are resolved then we know all threads are in sync and
1157+
// we can call the callback.
1158+
Promise.all(promises).then(() => {
1159+
PThread.outstandingPromises = {};
1160+
#if PTHREADS_DEBUG
1161+
dbg('_emscripten_dlsync_threads_async done: calling callback');
1162+
#endif
1163+
{{{ makeDynCall('vp', 'callback') }}}(ctx);
1164+
});
1165+
},
1166+
1167+
// Synchronous version dlsync_threads. Always run on the main thread.
1168+
_emscripten_dlsync_threads__deps: ['_emscripten_proxy_dlsync'],
1169+
_emscripten_dlsync_threads: function() {
1170+
#if ASSERTIONS
1171+
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads() can only ever be called from main thread');
1172+
#endif
1173+
for (const ptr of Object.keys(PThread.pthreads)) {
1174+
const pthread_ptr = Number(ptr);
1175+
if (!(pthread_ptr in PThread.zombieThreads)) {
1176+
__emscripten_proxy_dlsync(pthread_ptr);
1177+
}
1178+
}
1179+
},
1180+
#endif // MAIN_MODULE
1181+
10761182
$executeNotifiedProxyingQueue: function(queue) {
10771183
// Set the notification state to processing.
10781184
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
@@ -275,9 +275,6 @@ int emscripten_pthread_attr_settransferredcanvases(pthread_attr_t *a, const char
275275
// blocking is not enabled, see ALLOW_BLOCKING_ON_MAIN_THREAD.
276276
void emscripten_check_blocking_allowed(void);
277277

278-
// Experimental API for syncing loaded code between pthreads.
279-
void _emscripten_thread_sync_code();
280-
281278
#ifdef __cplusplus
282279
}
283280
#endif

0 commit comments

Comments
 (0)