Skip to content

Commit 4b83065

Browse files
committed
Simplify thread clean / exit / terminate logic. NFC.
Mostly inspired by musl.
1 parent 4bf115a commit 4b83065

File tree

7 files changed

+35
-66
lines changed

7 files changed

+35
-66
lines changed

src/library_pthread.js

Lines changed: 19 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
var LibraryPThread = {
3030
$PThread__postset: 'PThread.init();',
3131
$PThread__deps: ['_emscripten_thread_init',
32-
'$killThread',
3332
'$cancelThread', '$cleanupThread', '$zeroMemory',
3433
'$ptrToString', '$spawnThread',
3534
'_emscripten_thread_free_data',
@@ -157,7 +156,7 @@ var LibraryPThread = {
157156
for (var t in PThread.pthreads) {
158157
var pthread = PThread.pthreads[t];
159158
if (pthread && pthread.worker) {
160-
PThread.returnWorkerToPool(pthread.worker);
159+
cleanupThread(t, true);
161160
}
162161
}
163162

@@ -178,17 +177,21 @@ var LibraryPThread = {
178177
}
179178
PThread.unusedWorkers = [];
180179
},
181-
returnWorkerToPool: function(worker) {
180+
returnWorkerToPool: function(worker, terminate) {
182181
// We don't want to run main thread queued calls here, since we are doing
183182
// some operations that leave the worker queue in an invalid state until
184183
// we are completely done (it would be bad if free() ends up calling a
185184
// queued pthread_create which looks at the global data structures we are
186185
// modifying).
187186
PThread.runWithoutMainThreadQueuedCalls(function() {
188187
delete PThread.pthreads[worker.pthread.threadInfoStruct];
189-
// Note: worker is intentionally not terminated so the pool can
190-
// dynamically grow.
191-
PThread.unusedWorkers.push(worker);
188+
if (terminate) {
189+
worker.terminate();
190+
} else {
191+
// No termination needed? Return it to the worker pool as an
192+
// unused worker so the pool can dynamically grow.
193+
PThread.unusedWorkers.push(worker);
194+
}
192195
PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker), 1);
193196
// Not a running Worker anymore
194197
__emscripten_thread_free_data(worker.pthread.threadInfoStruct);
@@ -272,9 +275,7 @@ var LibraryPThread = {
272275
} else if (cmd === 'spawnThread') {
273276
spawnThread(d);
274277
} else if (cmd === 'cleanupThread') {
275-
cleanupThread(d['thread']);
276-
} else if (cmd === 'killThread') {
277-
killThread(d['thread']);
278+
cleanupThread(d['thread'], d['terminate']);
278279
} else if (cmd === 'cancelThread') {
279280
cancelThread(d['thread']);
280281
} else if (cmd === 'loaded') {
@@ -451,59 +452,23 @@ var LibraryPThread = {
451452
}
452453
},
453454

454-
$killThread__deps: ['_emscripten_thread_free_data'],
455-
$killThread: function(pthread_ptr) {
456-
#if PTHREADS_DEBUG
457-
err('killThread ' + ptrToString(pthread_ptr));
458-
#endif
459-
#if ASSERTIONS
460-
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! killThread() can only ever be called from main application thread!');
461-
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in killThread!');
462-
#endif
463-
{{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
464-
var pthread = PThread.pthreads[pthread_ptr];
465-
delete PThread.pthreads[pthread_ptr];
466-
pthread.worker.terminate();
467-
__emscripten_thread_free_data(pthread_ptr);
468-
// The worker was completely nuked (not just the pthread execution it was hosting), so remove it from running workers
469-
// but don't put it back to the pool.
470-
PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(pthread.worker), 1); // Not a running Worker anymore.
471-
pthread.worker.pthread = undefined;
472-
},
473-
474455
__emscripten_thread_cleanup: function(thread) {
475456
// Called when a thread needs to be cleaned up so it can be reused.
476457
// A thread is considered reusable when it either returns from its
477458
// entry point, calls pthread_exit, or acts upon a cancellation.
478459
// Detached threads are responsible for calling this themselves,
479460
// otherwise pthread_join is responsible for calling this.
480-
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread);
481-
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread });
461+
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread, false);
462+
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread, 'terminate': false });
482463
},
483464

484-
$cleanupThread: function(pthread_ptr) {
465+
$cleanupThread: function(pthread_ptr, terminate) {
485466
#if ASSERTIONS
486467
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! cleanupThread() can only ever be called from main application thread!');
487468
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cleanupThread!');
488469
#endif
489470
var pthread = PThread.pthreads[pthread_ptr];
490-
// If pthread has been removed from this map this also means that pthread_ptr points
491-
// to already freed data. Such situation may occur in following circumstance:
492-
// Joining thread from non-main browser thread (this also includes thread running main()
493-
// when compiled with `PROXY_TO_PTHREAD`) - in such situation it may happen that following
494-
// code flow occur (MB - Main Browser Thread, S1, S2 - Worker Threads):
495-
// S2: thread ends, 'exit' message is sent to MB
496-
// S1: calls pthread_join(S2), this causes:
497-
// a. S2 is marked as detached,
498-
// b. 'cleanupThread' message is sent to MB.
499-
// MB: handles 'exit' message, as thread is detached, so returnWorkerToPool()
500-
// is called and all thread related structs are freed/released.
501-
// MB: handles 'cleanupThread' message which calls this function.
502-
if (pthread) {
503-
{{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
504-
var worker = pthread.worker;
505-
PThread.returnWorkerToPool(worker);
506-
}
471+
if (pthread) PThread.returnWorkerToPool(pthread.worker, terminate);
507472
},
508473

509474
#if MAIN_MODULE
@@ -555,7 +520,7 @@ var LibraryPThread = {
555520
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cancelThread!');
556521
#endif
557522
var pthread = PThread.pthreads[pthread_ptr];
558-
pthread.worker.postMessage({ 'cmd': 'cancel' });
523+
if (pthread) pthread.worker.postMessage({ 'cmd': 'cancel' });
559524
},
560525

561526
$spawnThread: function(threadParams) {
@@ -842,17 +807,17 @@ var LibraryPThread = {
842807
err('pthread_kill attempted on a null thread pointer!');
843808
return {{{ cDefine('ESRCH') }}};
844809
}
845-
var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
846-
if (self !== thread) {
810+
var tid = {{{ makeGetValue('thread', C_STRUCTS.pthread.tid, 'i32') }}};
811+
if (!tid) {
847812
err('pthread_kill attempted on thread ' + ptrToString(thread) + ', which does not point to a valid thread, or does not exist anymore!');
848813
return {{{ cDefine('ESRCH') }}};
849814
}
850815
if (signal === {{{ cDefine('SIGCANCEL') }}}) { // Used by pthread_cancel in musl
851816
if (!ENVIRONMENT_IS_PTHREAD) cancelThread(thread);
852817
else postMessage({ 'cmd': 'cancelThread', 'thread': thread });
853818
} else if (signal != 0) {
854-
if (!ENVIRONMENT_IS_PTHREAD) killThread(thread);
855-
else postMessage({ 'cmd': 'killThread', 'thread': thread });
819+
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread, true);
820+
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread, 'terminate': true });
856821
}
857822
return 0;
858823
},

system/lib/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ tid_t GetTid() {
528528
#elif SANITIZER_SOLARIS
529529
return thr_self();
530530
#elif SANITIZER_EMSCRIPTEN
531-
return (tid_t) pthread_self();
531+
return gettid();
532532
#else
533533
return internal_syscall(SYSCALL(gettid));
534534
#endif

system/lib/libc/musl/src/internal/pthread_impl.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ struct pthread {
7777
#ifdef __EMSCRIPTEN__
7878
// If --threadprofiler is enabled, this pointer is allocated to contain
7979
// internal information about the thread state for profiling purposes.
80-
thread_profiler_block * _Atomic profilerBlock;
81-
int stack_owned;
80+
thread_profiler_block *profilerBlock;
81+
volatile int stack_owned;
8282
#endif
8383
};
8484

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ static int __pthread_detach(pthread_t t)
66
#ifdef __EMSCRIPTEN__
77
// XXX EMSCRIPTEN: Add check for invalid (already joined) thread. Again
88
// for the benefit of the conformance tests.
9-
if (t->self != t) return ESRCH;
9+
if (!t->tid) return ESRCH;
1010
#endif
1111
/* If the cas fails, detach state is either already-detached
1212
* or exiting/exited, and pthread_join will trap or cleanup. */

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ static int __pthread_timedjoin_np(pthread_t t, void **res, const struct timespec
1616
#ifdef __EMSCRIPTEN__
1717
// Attempt to join a thread which does not point to a valid thread, or
1818
// does not exist anymore.
19-
if (t->self != t) return ESRCH;
19+
if (!t->tid) return ESRCH;
2020
// Thread is attempting to join to itself. Already detached threads are
2121
// handled below by returning EINVAL instead.
2222
// TODO: The detached check here is just to satisfy the `other.test_{proxy,main}_pthread_join_detach` tests.

system/lib/pthread/library_pthread.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,7 @@ __attribute__((constructor(48)))
653653
void __emscripten_init_main_thread(void) {
654654
__emscripten_init_main_thread_js(&__main_pthread);
655655

656-
// The pthread struct has a field that points to itself - this is used as
657-
// a magic ID to detect whether the pthread_t structure is 'alive'.
656+
// The pthread struct has a field that points to itself.
658657
__main_pthread.self = &__main_pthread;
659658
__main_pthread.stack = (void*)emscripten_stack_get_base();
660659
__main_pthread.stack_size = emscripten_stack_get_base() - emscripten_stack_get_end();

system/lib/pthread/pthread_create.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,10 @@ int __pthread_create(pthread_t* restrict res,
104104
// zero-initialize thread structure.
105105
memset(new, 0, sizeof(struct pthread));
106106

107-
// The pthread struct has a field that points to itself - this is used as a
108-
// magic ID to detect whether the pthread_t structure is 'alive'.
107+
// The pthread struct has a field that points to itself.
109108
new->self = new;
109+
110+
// Thread ID, this becomes zero when the thread is no longer available.
110111
new->tid = next_tid++;
111112

112113
// pthread struct robust_list head should point to itself.
@@ -177,13 +178,17 @@ void _emscripten_thread_free_data(pthread_t t) {
177178
#ifndef NDEBUG
178179
if (t->profilerBlock) {
179180
emscripten_builtin_free(t->profilerBlock);
181+
t->profilerBlock = 0;
180182
}
181183
#endif
182-
if (t->stack_owned) {
184+
if (a_cas(&t->stack_owned, 1, 0)) {
183185
emscripten_builtin_free(((char*)t->stack) - t->stack_size);
184186
}
185-
// To aid in debugging set all fields to zero
186-
memset(t, 0, sizeof(*t));
187+
// The tid may be reused. Clear it to prevent inadvertent use and inform functions
188+
// that would use it that it's no longer available.
189+
t->tid = 0;
190+
// TODO(kleisauke): Should we set all fields to zero instead?:
191+
//memset(t, 0, sizeof(*t));
187192
emscripten_builtin_free(t);
188193
}
189194

0 commit comments

Comments
 (0)