Skip to content

Commit 36ec7f7

Browse files
committed
Simplify thread clean / exit / terminate logic. NFC.
Mostly inspired by musl.
1 parent 57ad420 commit 36ec7f7

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
@@ -7,7 +7,6 @@
77
var LibraryPThread = {
88
$PThread__postset: 'if (!ENVIRONMENT_IS_PTHREAD) PThread.initMainThread();',
99
$PThread__deps: ['_emscripten_thread_init',
10-
'$killThread',
1110
'$cancelThread', '$cleanupThread', '$zeroMemory',
1211
'_emscripten_thread_free_data',
1312
'exit',
@@ -96,7 +95,7 @@ var LibraryPThread = {
9695
for (var t in PThread.pthreads) {
9796
var pthread = PThread.pthreads[t];
9897
if (pthread && pthread.worker) {
99-
PThread.returnWorkerToPool(pthread.worker);
98+
cleanupThread(t, true);
10099
}
101100
}
102101

@@ -117,17 +116,21 @@ var LibraryPThread = {
117116
}
118117
PThread.unusedWorkers = [];
119118
},
120-
returnWorkerToPool: function(worker) {
119+
returnWorkerToPool: function(worker, terminate) {
121120
// We don't want to run main thread queued calls here, since we are doing
122121
// some operations that leave the worker queue in an invalid state until
123122
// we are completely done (it would be bad if free() ends up calling a
124123
// queued pthread_create which looks at the global data structures we are
125124
// modifying).
126125
PThread.runWithoutMainThreadQueuedCalls(function() {
127126
delete PThread.pthreads[worker.pthread.threadInfoStruct];
128-
// Note: worker is intentionally not terminated so the pool can
129-
// dynamically grow.
130-
PThread.unusedWorkers.push(worker);
127+
if (terminate) {
128+
worker.terminate();
129+
} else {
130+
// No termination needed? Return it to the worker pool as an
131+
// unused worker so the pool can dynamically grow.
132+
PThread.unusedWorkers.push(worker);
133+
}
131134
PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(worker), 1);
132135
// Not a running Worker anymore
133136
__emscripten_thread_free_data(worker.pthread.threadInfoStruct);
@@ -214,9 +217,7 @@ var LibraryPThread = {
214217
} else if (cmd === 'spawnThread') {
215218
spawnThread(d);
216219
} else if (cmd === 'cleanupThread') {
217-
cleanupThread(d['thread']);
218-
} else if (cmd === 'killThread') {
219-
killThread(d['thread']);
220+
cleanupThread(d['thread'], d['terminate']);
220221
} else if (cmd === 'cancelThread') {
221222
cancelThread(d['thread']);
222223
} else if (cmd === 'loaded') {
@@ -387,59 +388,23 @@ var LibraryPThread = {
387388
}
388389
},
389390

390-
$killThread__deps: ['_emscripten_thread_free_data'],
391-
$killThread: function(pthread_ptr) {
392-
#if PTHREADS_DEBUG
393-
err('killThread 0x' + pthread_ptr.toString(16));
394-
#endif
395-
#if ASSERTIONS
396-
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! killThread() can only ever be called from main application thread!');
397-
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in killThread!');
398-
#endif
399-
{{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
400-
var pthread = PThread.pthreads[pthread_ptr];
401-
delete PThread.pthreads[pthread_ptr];
402-
pthread.worker.terminate();
403-
__emscripten_thread_free_data(pthread_ptr);
404-
// The worker was completely nuked (not just the pthread execution it was hosting), so remove it from running workers
405-
// but don't put it back to the pool.
406-
PThread.runningWorkers.splice(PThread.runningWorkers.indexOf(pthread.worker), 1); // Not a running Worker anymore.
407-
pthread.worker.pthread = undefined;
408-
},
409-
410391
__emscripten_thread_cleanup: function(thread) {
411392
// Called when a thread needs to be cleaned up so it can be reused.
412393
// A thread is considered reusable when it either returns from its
413394
// entry point, calls pthread_exit, or acts upon a cancellation.
414395
// Detached threads are responsible for calling this themselves,
415396
// otherwise pthread_join is responsible for calling this.
416-
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread);
417-
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread });
397+
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread, false);
398+
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread, 'terminate': false });
418399
},
419400

420-
$cleanupThread: function(pthread_ptr) {
401+
$cleanupThread: function(pthread_ptr, terminate) {
421402
#if ASSERTIONS
422403
assert(!ENVIRONMENT_IS_PTHREAD, 'Internal Error! cleanupThread() can only ever be called from main application thread!');
423404
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cleanupThread!');
424405
#endif
425406
var pthread = PThread.pthreads[pthread_ptr];
426-
// If pthread has been removed from this map this also means that pthread_ptr points
427-
// to already freed data. Such situation may occur in following circumstance:
428-
// Joining thread from non-main browser thread (this also includes thread running main()
429-
// when compiled with `PROXY_TO_PTHREAD`) - in such situation it may happen that following
430-
// code flow occur (MB - Main Browser Thread, S1, S2 - Worker Threads):
431-
// S2: thread ends, 'exit' message is sent to MB
432-
// S1: calls pthread_join(S2), this causes:
433-
// a. S2 is marked as detached,
434-
// b. 'cleanupThread' message is sent to MB.
435-
// MB: handles 'exit' message, as thread is detached, so returnWorkerToPool()
436-
// is called and all thread related structs are freed/released.
437-
// MB: handles 'cleanupThread' message which calls this function.
438-
if (pthread) {
439-
{{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}};
440-
var worker = pthread.worker;
441-
PThread.returnWorkerToPool(worker);
442-
}
407+
if (pthread) PThread.returnWorkerToPool(pthread.worker, terminate);
443408
},
444409

445410
$registerTlsInit: function(tlsInitFunc, moduleExports, metadata) {
@@ -489,7 +454,7 @@ var LibraryPThread = {
489454
assert(pthread_ptr, 'Internal Error! Null pthread_ptr in cancelThread!');
490455
#endif
491456
var pthread = PThread.pthreads[pthread_ptr];
492-
pthread.worker.postMessage({ 'cmd': 'cancel' });
457+
if (pthread) pthread.worker.postMessage({ 'cmd': 'cancel' });
493458
},
494459

495460
$spawnThread: function(threadParams) {
@@ -768,17 +733,17 @@ var LibraryPThread = {
768733
err('pthread_kill attempted on a null thread pointer!');
769734
return {{{ cDefine('ESRCH') }}};
770735
}
771-
var self = {{{ makeGetValue('thread', C_STRUCTS.pthread.self, 'i32') }}};
772-
if (self !== thread) {
736+
var tid = {{{ makeGetValue('thread', C_STRUCTS.pthread.tid, 'i32') }}};
737+
if (!tid) {
773738
err('pthread_kill attempted on thread 0x' + thread.toString(16) + ', which does not point to a valid thread, or does not exist anymore!');
774739
return {{{ cDefine('ESRCH') }}};
775740
}
776741
if (signal === {{{ cDefine('SIGCANCEL') }}}) { // Used by pthread_cancel in musl
777742
if (!ENVIRONMENT_IS_PTHREAD) cancelThread(thread);
778743
else postMessage({ 'cmd': 'cancelThread', 'thread': thread });
779744
} else if (signal != 0) {
780-
if (!ENVIRONMENT_IS_PTHREAD) killThread(thread);
781-
else postMessage({ 'cmd': 'killThread', 'thread': thread });
745+
if (!ENVIRONMENT_IS_PTHREAD) cleanupThread(thread, true);
746+
else postMessage({ 'cmd': 'cleanupThread', 'thread': thread, 'terminate': true });
782747
}
783748
return 0;
784749
},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ tid_t GetTid() {
536536
#elif SANITIZER_SOLARIS
537537
return thr_self();
538538
#elif SANITIZER_EMSCRIPTEN
539-
return (tid_t) pthread_self();
539+
return gettid();
540540
#else
541541
return internal_syscall(SYSCALL(gettid));
542542
#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
@@ -814,8 +814,7 @@ __attribute__((constructor(48)))
814814
void __emscripten_init_main_thread(void) {
815815
__emscripten_init_main_thread_js(&__main_pthread);
816816

817-
// The pthread struct has a field that points to itself - this is used as
818-
// a magic ID to detect whether the pthread_t structure is 'alive'.
817+
// The pthread struct has a field that points to itself.
819818
__main_pthread.self = &__main_pthread;
820819
__main_pthread.stack = (void*)emscripten_stack_get_base();
821820
__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)