Skip to content

Commit d2bb705

Browse files
committed
Fix pthread wait issues with cancellation which caused semaphores not being waitable in pthread cancellation handlers.
1 parent aff64f2 commit d2bb705

File tree

6 files changed

+47
-19
lines changed

6 files changed

+47
-19
lines changed

src/library_pthread.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,32 @@ var LibraryPThread = {
5151
// Called when we are performing a pthread_exit(), either explicitly called by programmer,
5252
// or implicitly when leaving the thread main function.
5353
threadExit: function(exitCode) {
54-
PThread.runExitHandlers();
55-
// No-op in the main thread. Note: Spec says we should join() all child threads, but since we don't have join,
56-
// we might at least cancel all threads.
57-
if (!ENVIRONMENT_IS_PTHREAD) return 0;
58-
59-
if (threadBlock) { // If we haven't yet exited?
60-
Atomics.store(HEAPU32, (threadBlock + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, exitCode);
54+
var tb = _pthread_self();
55+
if (tb) { // If we haven't yet exited?
56+
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, exitCode);
6157
// When we publish this, the main thread is free to deallocate the thread object and we are done.
6258
// Therefore set threadBlock = 0; above to 'release' the object in this worker thread.
63-
Atomics.store(HEAPU32, (threadBlock + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1);
64-
_emscripten_futex_wake(threadBlock + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}});
59+
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1);
60+
61+
// Disable all cancellation so that executing the cleanup handlers won't trigger another JS
62+
// canceled exception to be thrown.
63+
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2, 1);
64+
65+
PThread.runExitHandlers();
66+
67+
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}});
6568
threadBlock = 0;
66-
postMessage({ cmd: 'exit' });
69+
if (ENVIRONMENT_IS_PTHREAD) {
70+
postMessage({ cmd: 'exit' });
71+
}
6772
}
6873
},
6974

7075
threadCancel: function() {
7176
PThread.runExitHandlers();
7277
Atomics.store(HEAPU32, (threadBlock + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, -1/*PTHREAD_CANCELED*/);
78+
Atomics.store(HEAPU32, (threadBlock + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); // Mark the thread as no longer running.
79+
_emscripten_futex_wake(threadBlock + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads
7380
threadBlock = selfThreadId = 0; // Not hosting a pthread anymore in this worker, reset the info structures to null.
7481
postMessage({ cmd: 'cancelDone' });
7582
},
@@ -573,7 +580,9 @@ var LibraryPThread = {
573580
// Returns 0 on success, or one of the values -ETIMEDOUT, -EWOULDBLOCK or -EINVAL on error.
574581
emscripten_futex_wait: function(addr, val, timeout) {
575582
if (addr <= 0 || addr > HEAP8.length || addr&3 != 0) return -{{{ cDefine('EINVAL') }}};
583+
// dump('futex_wait addr:' + addr + ' by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n');
576584
var ret = Atomics.futexWait(HEAP32, addr >> 2, val, timeout);
585+
// dump('futex_wait done\n');
577586
if (ret == Atomics.TIMEDOUT) return -{{{ cDefine('ETIMEDOUT') }}};
578587
if (ret == Atomics.NOTEQUAL) return -{{{ cDefine('EWOULDBLOCK') }}};
579588
if (ret == 0) return 0;
@@ -584,6 +593,7 @@ var LibraryPThread = {
584593
// Pass count == INT_MAX to wake up all threads.
585594
emscripten_futex_wake: function(addr, count) {
586595
if (addr <= 0 || addr > HEAP8.length || addr&3 != 0 || count < 0) return -{{{ cDefine('EINVAL') }}};
596+
// dump('futex_wake addr:' + addr + ' by thread: ' + _pthread_self() + (ENVIRONMENT_IS_PTHREAD?'(pthread)':'') + '\n');
587597
var ret = Atomics.futexWake(HEAP32, addr >> 2, count);
588598
if (ret >= 0) return ret;
589599
throw 'Atomics.futexWake returned an unexpected value ' + ret;

src/pthread-main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,13 @@ this.onmessage = function(e) {
7373
else
7474
result = asm.dynCall_i(e.data.start_routine); // as a hack, try signature 'i' as fallback.
7575
} catch(e) {
76-
Atomics.store(HEAPU32, (threadBlock + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running.
77-
_emscripten_futex_wake(threadBlock + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/, 9999); // wake all threads
7876
if (e === 'Canceled!') {
7977
PThread.threadCancel();
8078
return;
8179
} else {
8280
Atomics.store(HEAPU32, (threadBlock + 4 /*{{{ C_STRUCTS.pthread.threadExitCode }}}*/ ) >> 2, -2 /*A custom entry specific to Emscripten denoting that the thread crashed.*/);
81+
Atomics.store(HEAPU32, (threadBlock + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/ ) >> 2, 1); // Mark the thread as no longer running.
82+
_emscripten_futex_wake(threadBlock + 0 /*{{{ C_STRUCTS.pthread.threadStatus }}}*/, 0x7FFFFFFF/*INT_MAX*/); // wake all threads
8383
throw e;
8484
}
8585
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,14 @@ static int do_wait(volatile int *addr, int val,
3535
}
3636

3737
#ifdef __EMSCRIPTEN__
38-
if (pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS) {
38+
if (1 || pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS) {
3939
do {
40-
if (_pthread_isduecanceled(pthread_self())) return EINTR;
40+
if (_pthread_isduecanceled(pthread_self())) {
41+
// Emscripten-specific return value: The wait was canceled by user calling
42+
// pthread_cancel() for this thread, and the caller needs to cooperatively
43+
// cancel execution.
44+
return ECANCELED;
45+
}
4146
// Must wait in slices in case this thread is cancelled in between.
4247
double waitNsecs = at ? _pthread_nsecs_until(at) : INFINITY;
4348
if (waitNsecs <= 0) {
@@ -73,9 +78,5 @@ int __timedwait(volatile int *addr, int val,
7378
pthread_cleanup_pop(0);
7479
if (!cleanup) pthread_setcancelstate(cs, 0);
7580

76-
#ifdef __EMSCRIPTEN__
77-
// XXX Emscripten: since we don't have signals, cooperatively test cancellation.
78-
if (pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS) pthread_testcancel();
79-
#endif
8081
return r;
8182
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ int pthread_cond_broadcast(pthread_cond_t *c)
88

99
a_inc(&c->_c_seq);
1010

11+
#ifdef __EMSCRIPTEN__
12+
// XXX Emscripten: TODO: This is suboptimal but works naively correctly for now. The Emscripten-specific code path below
13+
// has a bug and does not work for some reason. Figure it out and remove this code block.
14+
__wake(&c->_c_seq, -1, 0);
15+
return 0;
16+
#endif
17+
1118
/* If cond var is process-shared, simply wake all waiters. */
1219
if (c->_c_mutex == (void *)-1) {
1320
__wake(&c->_c_seq, -1, 0);
@@ -28,6 +35,7 @@ int pthread_cond_broadcast(pthread_cond_t *c)
2835
#ifdef __EMSCRIPTEN__
2936
int futexResult;
3037
do {
38+
// XXX Emscripten: Bug, this does not work correctly.
3139
futexResult = emscripten_futex_wake_or_requeue(&c->_c_seq, !m->_m_type || (m->_m_lock&INT_MAX)!=pthread_self()->tid,
3240
c->_c_seq, &m->_m_lock);
3341
} while(futexResult == -EAGAIN);

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,19 @@ int pthread_cond_timedwait(pthread_cond_t *restrict c, pthread_mutex_t *restrict
6464

6565
pthread_mutex_unlock(m);
6666

67-
do e = __timedwait(&c->_c_seq, seq, c->_c_clock, ts, cleanup, &cm, 0);
67+
do {
68+
e = __timedwait(&c->_c_seq, seq, c->_c_clock, ts, cleanup, &cm, 0);
69+
}
6870
while (c->_c_seq == seq && (!e || e==EINTR));
6971
if (e == EINTR) e = 0;
7072

7173
unwait(c, m);
7274

7375
if ((r=pthread_mutex_lock(m))) return r;
7476

77+
#ifdef __EMSCRIPTEN__
78+
pthread_testcancel();
79+
#endif
80+
7581
return e;
7682
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict at)
1313
a_inc(sem->__val+1);
1414
a_cas(sem->__val, 0, -1);
1515
r = __timedwait(sem->__val, -1, CLOCK_REALTIME, at, cleanup, sem->__val+1, 0);
16+
#ifdef __EMSCRIPTEN__
17+
if (r == ECANCELED) r = EINTR;
18+
#endif
1619
a_dec(sem->__val+1);
1720
if (r) {
1821
errno = r;

0 commit comments

Comments
 (0)