Skip to content

Commit 944c432

Browse files
committed
Block in dlopen until all threads have loaded the module
Fixes: #18345
1 parent 4bff735 commit 944c432

File tree

15 files changed

+470
-146
lines changed

15 files changed

+470
-146
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 ASSERTIONS
5968
nextWorkerID: 1,
6069
debugInit: function() {
@@ -269,6 +278,10 @@ var LibraryPThread = {
269278
spawnThread(d);
270279
} else if (cmd === 'cleanupThread') {
271280
cleanupThread(d['thread']);
281+
#if MAIN_MODULE
282+
} else if (cmd === 'markAsZombie') {
283+
markAsZombie(d['thread']);
284+
#endif
272285
} else if (cmd === 'killThread') {
273286
killThread(d['thread']);
274287
} else if (cmd === 'cancelThread') {
@@ -540,11 +553,20 @@ var LibraryPThread = {
540553
},
541554

542555
$cleanupThread: function(pthread_ptr) {
556+
#if PTHREADS_DEBUG
557+
dbg('cleanupThread: ' + ptrToString(pthread_ptr))
558+
#endif
543559
#if ASSERTIONS
544560
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! cleanupThread() can only ever be called from main application thread!');
545561
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cleanupThread!');
546562
#endif
547563
var worker = PThread.pthreads[pthread_ptr];
564+
#if MAIN_MODULE
565+
delete PThread.zombieThreads[pthread_ptr];
566+
if (pthread_ptr in PThread.outstandingPromises) {
567+
PThread.outstandingPromises[pthread_ptr].resolve();
568+
}
569+
#endif
548570
assert(worker);
549571
PThread.returnWorkerToPool(worker);
550572
},
@@ -1046,7 +1068,7 @@ var LibraryPThread = {
10461068
// Before we call the thread entry point, make sure any shared libraries
10471069
// have been loaded on this there. Otherwise our table migth be not be
10481070
// in sync and might not contain the function pointer `ptr` at all.
1049-
__emscripten_thread_sync_code();
1071+
__emscripten_dlsync_self();
10501072
#endif
10511073
// pthread entry points are always of signature 'void *ThreadMain(void *arg)'
10521074
// Native codebases sometimes spawn threads with other thread entry point
@@ -1075,6 +1097,90 @@ var LibraryPThread = {
10751097
#endif
10761098
},
10771099

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