Skip to content

Commit 543b474

Browse files
committed
Block in dlopen until all threads have loaded the module
Fixes: #18345
1 parent 801e663 commit 543b474

File tree

15 files changed

+475
-146
lines changed

15 files changed

+475
-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: 112 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+
'$markAsFinshed',
36+
#endif
3437
'$spawnThread',
3538
'_emscripten_thread_free_data',
3639
'exit',
@@ -97,6 +100,12 @@ var LibraryPThread = {
97100
while (pthreadPoolSize--) {
98101
PThread.allocateUnusedWorker();
99102
}
103+
#endif
104+
#if MAIN_MODULE
105+
PThread.outstandingPromises = {};
106+
// Finished threads are threads that have finished running but we not yet
107+
// joined.
108+
PThread.finishedThreads = new Set();
100109
#endif
101110
},
102111

@@ -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 === 'markAsFinshed') {
283+
markAsFinshed(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+
PThread.finishedThreads.delete(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,95 @@ 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. We mark these threads
1103+
// as finished, which means that are in state where are no longer actually
1104+
// runnning, but remain around waiting to be joined. In this state they
1105+
// cannot run any more proxied work.
1106+
if (!ENVIRONMENT_IS_PTHREAD) markAsFinshed(thread);
1107+
else postMessage({ 'cmd': 'markAsFinshed', 'thread': thread });
1108+
},
1109+
1110+
$markAsFinshed: function(pthread_ptr) {
1111+
#if PTHREADS_DEBUG
1112+
dbg('markAsFinshed: ' + ptrToString(pthread_ptr));
1113+
#endif
1114+
PThread.finishedThreads.add(pthread_ptr);
1115+
if (pthread_ptr in PThread.outstandingPromises) {
1116+
PThread.outstandingPromises[pthread_ptr].resolve();
1117+
}
1118+
},
1119+
1120+
// Asynchronous version dlsync_threads. Always run on the main thread.
1121+
// This work happens asynchronously. The `callback` is called once this work
1122+
// is completed, passing the ctx.
1123+
// TODO(sbc): Should we make a new form of __proxy attribute for JS library
1124+
// function that run asynchronously like but blocks the caller until they are
1125+
// done. Perhaps "sync_with_ctx"?
1126+
_emscripten_dlsync_threads_async__sig: 'viii',
1127+
_emscripten_dlsync_threads_async__deps: ['_emscripten_proxy_dlsync_async', '$newNativePromise'],
1128+
_emscripten_dlsync_threads_async: function(caller, callback, ctx) {
1129+
#if PTHREADS_DEBUG
1130+
dbg("_emscripten_dlsync_threads_async caller=" + ptrToString(caller));
1131+
#endif
1132+
#if ASSERTIONS
1133+
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads_async() can only ever be called from main thread');
1134+
#endif
1135+
1136+
const promises = [];
1137+
assert(Object.keys(PThread.outstandingPromises).length === 0);
1138+
1139+
// This first promise resolves once the main thread has loaded all modules.
1140+
promises.push(newNativePromise(__emscripten_dlsync_self_async, null).promise);
1141+
1142+
// We then create a sequence of promises, one per thread, that resolve once
1143+
// each thread has performed its sync using _emscripten_proxy_dlsync.
1144+
// Any new threads that are created after this call will automaticaly be
1145+
// in sync because we call `__emscripten_dlsync_self` in
1146+
// invokeEntryPoint before the threads entry point is called.
1147+
for (const ptr of Object.keys(PThread.pthreads)) {
1148+
const pthread_ptr = Number(ptr);
1149+
if (pthread_ptr !== caller && !PThread.finishedThreads.has(pthread_ptr)) {
1150+
var p = newNativePromise(__emscripten_proxy_dlsync_async, pthread_ptr);
1151+
PThread.outstandingPromises[pthread_ptr] = p;
1152+
promises.push(p.promise);
1153+
}
1154+
}
1155+
1156+
#if PTHREADS_DEBUG
1157+
dbg('_emscripten_dlsync_threads_async: waiting on ' + promises.length + ' promises');
1158+
#endif
1159+
// Once all promises are resolved then we know all threads are in sync and
1160+
// we can call the callback.
1161+
Promise.all(promises).then(() => {
1162+
PThread.outstandingPromises = {};
1163+
#if PTHREADS_DEBUG
1164+
dbg('_emscripten_dlsync_threads_async done: calling callback');
1165+
#endif
1166+
{{{ makeDynCall('vp', 'callback') }}}(ctx);
1167+
});
1168+
},
1169+
1170+
// Synchronous version dlsync_threads. This is only needed for the case then
1171+
// the main thread call dlopen and in that case we have not choice but to
1172+
// synchronously block the main thread until all other threads are in sync.
1173+
// When `dlopen` is called from a worker, the worker itself is blocked but
1174+
// the operation its waiting on (on the main thread) can be async.
1175+
_emscripten_dlsync_threads__deps: ['_emscripten_proxy_dlsync'],
1176+
_emscripten_dlsync_threads: function() {
1177+
#if ASSERTIONS
1178+
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! _emscripten_dlsync_threads() can only ever be called from main thread');
1179+
#endif
1180+
for (const ptr of Object.keys(PThread.pthreads)) {
1181+
const pthread_ptr = Number(ptr);
1182+
if (!PThread.finishedThreads.has(pthread_ptr)) {
1183+
__emscripten_proxy_dlsync(pthread_ptr);
1184+
}
1185+
}
1186+
},
1187+
#endif // MAIN_MODULE
1188+
10781189
$executeNotifiedProxyingQueue: function(queue) {
10791190
// Set the notification state to processing.
10801191
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)