Skip to content

Commit 4c58245

Browse files
committed
Simplify thread clean / exit / terminate logic
1 parent 92c001d commit 4c58245

File tree

3 files changed

+40
-57
lines changed

3 files changed

+40
-57
lines changed

src/library_pthread.js

Lines changed: 38 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var LibraryPThread = {
88
$PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThreadBlock();',
99
$PThread__deps: ['_emscripten_thread_init',
1010
'emscripten_register_main_browser_thread_id',
11-
'$ERRNO_CODES', 'emscripten_futex_wake', '$killThread',
11+
'$ERRNO_CODES', 'emscripten_futex_wake',
1212
'$cancelThread', '$cleanupThread'
1313
#if USE_ASAN || USE_LSAN
1414
, '$withBuiltinMalloc'
@@ -191,7 +191,7 @@ var LibraryPThread = {
191191
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.detach_state }}} ) >> 2, {{{ cDefine('DT_EXITED') }}}); // Mark the thread as no longer running.
192192

193193
// Wake any joiner.
194-
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.detach_state }}}, {{{ cDefine('INT_MAX') }}});
194+
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.detach_state }}}, 1);
195195

196196
// Not hosting a pthread anymore in this worker, reset the info structures to null.
197197
__emscripten_thread_init(0, 0, 0); // Unregister the thread block also inside the asm.js scope.
@@ -209,24 +209,24 @@ var LibraryPThread = {
209209
if (ENVIRONMENT_IS_PTHREAD) {
210210
// Note: in theory we would like to return any offscreen canvases back to the main thread,
211211
// but if we ever fetched a rendering context for them that would not be valid, so we don't try.
212-
postMessage({ 'cmd': 'exit' });
212+
postMessage({ 'cmd': 'cleanupThread', 'thread': tb, 'terminate': false });
213213
}
214214
}
215215
},
216216

217+
terminateAllThreads__deps: ['$cleanupThread'],
217218
terminateAllThreads: function() {
218219
for (var t in PThread.pthreads) {
219-
var pthread = PThread.pthreads[t];
220-
if (pthread && pthread.worker) {
221-
PThread.returnWorkerToPool(pthread.worker);
220+
if (t && t.worker) {
221+
cleanupThread(t, true);
222222
}
223223
}
224224
PThread.pthreads = {};
225225

226226
for (var i = 0; i < PThread.unusedWorkers.length; ++i) {
227227
var worker = PThread.unusedWorkers[i];
228228
#if ASSERTIONS
229-
assert(!worker.pthread); // This Worker should not be hosting a pthread at this time.
229+
assert(!worker.pthread, 'This Worker should not be hosting a pthread at this time');
230230
#endif
231231
worker.terminate();
232232
}
@@ -238,14 +238,14 @@ var LibraryPThread = {
238238
#if ASSERTIONS
239239
assert(pthread, 'This Worker should have a pthread it is executing');
240240
#endif
241-
PThread.freeThreadData(pthread);
242-
worker.terminate();
241+
cleanupThread(pthread, true);
243242
}
244243
PThread.runningWorkers = [];
245244
},
246245
freeThreadData: function(pthread) {
247246
if (!pthread) return;
248247
if (pthread.threadInfoStruct) {
248+
// Free thread-specific data
249249
var tlsMemory = {{{ makeGetValue('pthread.threadInfoStruct', C_STRUCTS.pthread.tsd, 'i32') }}};
250250
{{{ makeSetValue('pthread.threadInfoStruct', C_STRUCTS.pthread.tsd, 0, 'i32') }}};
251251
#if PTHREADS_PROFILING
@@ -261,19 +261,23 @@ var LibraryPThread = {
261261
pthread.stackBase = 0;
262262
if (pthread.worker) pthread.worker.pthread = null;
263263
},
264-
returnWorkerToPool: function(worker) {
264+
returnWorkerToPool: function(worker, terminate) {
265265
// We don't want to run main thread queued calls here, since we are doing
266266
// some operations that leave the worker queue in an invalid state until
267267
// we are completely done (it would be bad if free() ends up calling a
268268
// queued pthread_create which looks at the global data structures we are
269269
// modifying).
270270
PThread.runWithoutMainThreadQueuedCalls(function() {
271271
delete PThread.pthreads[worker.pthread.threadInfoStruct];
272-
//Note: worker is intentionally not terminated so the pool can dynamically grow.
273-
PThread.unusedWorkers.push(worker);
272+
if (terminate) {
273+
worker.terminate();
274+
} else {
275+
// No termination needed? Return it to the worker pool as an unused worker.
276+
PThread.unusedWorkers.push(worker);
277+
}
274278
PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker), 1); // Not a running Worker anymore
275279
PThread.freeThreadData(worker.pthread);
276-
// Detach the worker from the pthread object, and return it to the worker pool as an unused worker.
280+
// Detach the worker from the pthread object.
277281
worker.pthread = undefined;
278282
});
279283
},
@@ -345,9 +349,7 @@ var LibraryPThread = {
345349
} else if (cmd === 'spawnThread') {
346350
spawnThread(e.data);
347351
} else if (cmd === 'cleanupThread') {
348-
cleanupThread(d['thread']);
349-
} else if (cmd === 'killThread') {
350-
killThread(d['thread']);
352+
cleanupThread(d['thread'], d['terminate']);
351353
} else if (cmd === 'cancelThread') {
352354
cancelThread(d['thread']);
353355
} else if (cmd === 'loaded') {
@@ -364,12 +366,6 @@ var LibraryPThread = {
364366
err('Thread ' + d['threadId'] + ': ' + d['text']);
365367
} else if (cmd === 'alert') {
366368
alert('Thread ' + d['threadId'] + ': ' + d['text']);
367-
} else if (cmd === 'exit') {
368-
// Detect whether the pthread_t structure is 'alive', it might have already been cleaned up
369-
var alive = worker.pthread && Atomics.load(HEAPU32, (worker.pthread.threadInfoStruct + {{{ C_STRUCTS.pthread.self }}}) >> 2);
370-
if (alive) {
371-
PThread.returnWorkerToPool(worker);
372-
}
373369
} else if (cmd === 'exitProcess') {
374370
// A pthread has requested to exit the whole application process (runtime).
375371
#if ASSERTIONS
@@ -478,41 +474,28 @@ var LibraryPThread = {
478474
}
479475
},
480476

481-
$killThread: function(pthread_ptr) {
482-
if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! killThread() can only ever be called from main application thread!';
483-
if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in killThread!';
484-
{{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
485-
var pthread = PThread.pthreads[pthread_ptr];
486-
pthread.worker.terminate();
487-
PThread.freeThreadData(pthread);
488-
// The worker was completely nuked (not just the pthread execution it was hosting), so remove it from running workers
489-
// but don't put it back to the pool.
490-
PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(pthread.worker), 1); // Not a running Worker anymore.
491-
pthread.worker.pthread = undefined;
492-
},
493-
494-
$cleanupThread: function(pthread_ptr) {
477+
$cleanupThread: function(pthread_ptr, terminate) {
495478
if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! cleanupThread() can only ever be called from main application thread!';
496479
if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in cleanupThread!';
497480
var pthread = PThread.pthreads[pthread_ptr];
498-
// If pthread has been removed from this map this also means that pthread_ptr points
499-
// to already freed data. Such situation may occur in following circumstances:
481+
// Check whether pthread_ptr points to already freed data. Such situation may occur in
482+
// following circumstances:
500483
// 1. Joining cancelled thread - in such situation it may happen that pthread data will
501484
// already be removed by handling 'cancelDone' message.
502485
// 2. Joining thread from non-main browser thread (this also includes thread running main()
503486
// when compiled with `PROXY_TO_PTHREAD`) - in such situation it may happen that following
504487
// code flow occur (MB - Main Browser Thread, S1, S2 - Worker Threads):
505-
// S2: thread ends, 'exit' message is sent to MB
488+
// S2: thread ends, 'cleanupThread' message is sent to MB
506489
// S1: calls pthread_join(S2), this causes:
507490
// a. S2 is marked as detached,
508491
// b. 'cleanupThread' message is sent to MB.
509-
// MB: handles 'exit' message, as thread is detached, so returnWorkerToPool()
510-
// is called and all thread related structs are freed/released.
511-
// MB: handles 'cleanupThread' message which calls this function.
512-
if (pthread) {
513-
{{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
492+
// MB: handles first 'cleanupThread' message, as thread is detached, so
493+
// returnWorkerToPool() is called and all thread related structs are freed/released.
494+
// MB: handles second 'cleanupThread' message which calls this function.
495+
var alive = pthread && Atomics.exchange(HEAPU32, (pthread_ptr + {{{ C_STRUCTS.pthread.self }}} ) >> 2, 0);
496+
if (alive) {
514497
var worker = pthread.worker;
515-
PThread.returnWorkerToPool(worker);
498+
PThread.returnWorkerToPool(worker, terminate);
516499
}
517500
},
518501

@@ -768,9 +751,9 @@ var LibraryPThread = {
768751
// magic ID to detect whether the pthread_t structure is 'alive'.
769752
{{{ makeSetValue('threadInfoStruct', C_STRUCTS.pthread.self, 'threadInfoStruct', 'i32') }}};
770753

771-
// pthread struct prev and next should point to itself - this is used by
772-
// pthread_key_delete for deleting thread-specific data.
773-
{{{ makeSetValue('threadInfoStruct', C_STRUCTS.pthread.prev, 0, 'i32') }}};
754+
// pthread struct prev and next should initially point to itself (see __init_tp in musl),
755+
// this is used by pthread_key_delete for deleting thread-specific data.
756+
{{{ makeSetValue('threadInfoStruct', C_STRUCTS.pthread.prev, 'threadInfoStruct', 'i32') }}};
774757
{{{ makeSetValue('threadInfoStruct', C_STRUCTS.pthread.next, 'threadInfoStruct', 'i32') }}};
775758

776759
// pthread struct robust_list head should point to itself.
@@ -843,7 +826,7 @@ var LibraryPThread = {
843826
},
844827
#endif
845828

846-
pthread_kill__deps: ['$killThread', '$cancelThread', 'emscripten_main_browser_thread_id'],
829+
pthread_kill__deps: ['$cancelThread', '$cleanupThread', 'emscripten_main_browser_thread_id'],
847830
pthread_kill: function(thread, signal) {
848831
if (signal < 0 || signal >= 65/*_NSIG*/) return ERRNO_CODES.EINVAL;
849832
if (thread === _emscripten_main_browser_thread_id()) {
@@ -864,8 +847,8 @@ var LibraryPThread = {
864847
if (!ENVIRONMENT_IS_PTHREAD) cancelThread(thread);
865848
else postMessage({ 'cmd': 'cancelThread', 'thread': thread});
866849
} else if (signal != 0) {
867-
if (!ENVIRONMENT_IS_PTHREAD) killThread(thread);
868-
else postMessage({ 'cmd': 'killThread', 'thread': thread});
850+
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread, true);
851+
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread, 'terminate': true });
869852
}
870853
return 0;
871854
},
@@ -1085,11 +1068,11 @@ var LibraryPThread = {
10851068
},
10861069

10871070
emscripten_cleanup_thread__asm: true,
1088-
emscripten_cleanup_thread__sig: 'vi',
1071+
emscripten_cleanup_thread__sig: 'vii',
10891072
emscripten_cleanup_thread__deps: ['$cleanupThread'],
1090-
emscripten_cleanup_thread: function(thread) {
1091-
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread);
1092-
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread });
1073+
emscripten_cleanup_thread: function(thread, terminate) {
1074+
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread, terminate);
1075+
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread, 'terminate': terminate });
10931076
},
10941077

10951078
emscripten_conditional_set_current_thread_status_js: function(expectedStatus, newStatus) {

system/include/emscripten/threading.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ void emscripten_thread_sleep(double msecs);
383383
// Cleanups the pthread_t struct.
384384
// TODO(kleisauke): Improve description.
385385
// This is an internal function and generally not intended for user code.
386-
void emscripten_cleanup_thread(pthread_t thread);
386+
void emscripten_cleanup_thread(pthread_t thread, int terminate);
387387

388388
#define EM_THREAD_STATUS int
389389
#define EM_THREAD_STATUS_NOTSTARTED 0

system/lib/libc/musl/src/thread/pthread_join.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ static int __pthread_timedjoin_np(pthread_t t, void **res, const struct timespec
4444
__tl_sync(t);
4545
if (res) *res = t->result;
4646
#ifdef __EMSCRIPTEN__ // XXX Emscripten cleanup thread
47-
if (state == DT_EXITED) emscripten_cleanup_thread(t);
47+
if (state == DT_EXITED) emscripten_cleanup_thread(t, 0);
4848
#else // XXX Emscripten map_base unused
4949
if (t->map_base) __munmap(t->map_base, t->map_size);
5050
#endif

0 commit comments

Comments
 (0)