Skip to content

Commit 9ad683b

Browse files
authored
Fix for async cancellation during __timedwait. NFC (#19963)
Emscripten doesn't have real async cancellation but we don't want __timedwait_cp to return the thread has been canceled while while its running. This fixes a problem in the test_pthread_setcanceltype_1_1 test which was not being correctly reported but shows up as a real problem with the switch to the new proxying system in #19947. Specifically, Without this change, a thread this async canceled during pthread_mutex_lock call will return ECANCELED, but instead it should never return and thread should be canceled, which is the behaviour after this change.
1 parent 00f4e0b commit 9ad683b

File tree

6 files changed

+111
-4
lines changed

6 files changed

+111
-4
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,18 @@ int __timedwait_cp(volatile int *addr, int val,
6969
// which may be either done by the user of __timedwait() function.
7070
if (is_runtime_thread ||
7171
pthread_self()->canceldisable != PTHREAD_CANCEL_DISABLE ||
72-
pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS) {
72+
pthread_self()->cancelasync) {
7373
double sleepUntilTime = emscripten_get_now() + msecsToSleep;
7474
do {
7575
if (pthread_self()->cancel) {
76-
// Emscripten-specific return value: The wait was canceled by user calling
77-
// pthread_cancel() for this thread, and the caller needs to cooperatively
78-
// cancel execution.
76+
// The thread was canceled by pthread_cancel().
77+
// In the case of cancelasync or PTHREAD_CANCEL_ENABLE we can just call
78+
// __pthread_testcancel(), which won't return at all.
79+
__pthread_testcancel();
80+
// If __pthread_testcancel does return here it means that canceldisable
81+
// must be set to PTHREAD_CANCEL_MASKED. This appear to mean "return
82+
// ECANCELLED to the caller". See pthread_cond_timedwait.c for the only
83+
// use of this that I could find.
7984
return ECANCELED;
8085
}
8186
msecsToSleep = sleepUntilTime - emscripten_get_now();

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ hidden long __cancel(), __syscall_cp_asm(), __syscall_cp_c();
88
long __cancel()
99
{
1010
pthread_t self = __pthread_self();
11+
#ifdef __EMSCRIPTEN__
12+
// Emscripten doesn't have actual async cancelation so we make a best effort
13+
// by cancelling cooperatively when self->cancelasync is set.
1114
if (self->canceldisable == PTHREAD_CANCEL_ENABLE || self->cancelasync)
15+
#else
16+
if (self->canceldisable == PTHREAD_CANCEL_ENABLE)
17+
#endif
1218
pthread_exit(PTHREAD_CANCELED);
1319
self->canceldisable = PTHREAD_CANCEL_DISABLE;
1420
return -ECANCELED;
@@ -77,7 +83,12 @@ static void cancel_handler(int sig, siginfo_t *si, void *ctx)
7783
void __testcancel()
7884
{
7985
pthread_t self = __pthread_self();
86+
#ifdef __EMSCRIPTEN__
87+
// See comment above about cancelasync under emscripten.
88+
if (self->cancel && (self->cancelasync || !self->canceldisable))
89+
#else
8090
if (self->cancel && !self->canceldisable)
91+
#endif
8192
__cancel();
8293
}
8394

test/pthread/test_pthread_cancel.out

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Canceling thread..
2+
Thread started!
3+
Called clean-up handler with arg 42
4+
After canceling, shared variable = 1.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2015 The Emscripten Authors. All rights reserved.
2+
// Emscripten is available under two separate licenses, the MIT license and the
3+
// University of Illinois/NCSA Open Source License. Both these licenses can be
4+
// found in the LICENSE file.
5+
6+
#include <pthread.h>
7+
#include <sys/types.h>
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include <stdbool.h>
11+
#include <assert.h>
12+
#include <unistd.h>
13+
#include <errno.h>
14+
#include <emscripten/console.h>
15+
16+
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
17+
_Atomic long res = 43;
18+
_Atomic int started = false;
19+
20+
static void cleanup_handler(void *arg)
21+
{
22+
long a = (long)arg;
23+
emscripten_outf("Called clean-up handler with arg %ld", a);
24+
res -= a;
25+
}
26+
27+
static void *thread_start(void *arg) {
28+
// Setup thread for async cancelation only
29+
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
30+
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
31+
32+
pthread_cleanup_push(cleanup_handler, (void*)42);
33+
34+
emscripten_out("Thread started!");
35+
36+
// Signal the main thread that are started
37+
started = true;
38+
39+
// This mutex is locked by the main thread so this call should never return.
40+
// pthread_mutex_lock is not a cancellation point so deferred cancellation
41+
// won't work here, async cancelation should.
42+
pthread_mutex_lock(&mutex);
43+
44+
assert(false && "pthread_mutex_lock returned!");
45+
pthread_cleanup_pop(0);
46+
}
47+
48+
int main() {
49+
pthread_mutex_lock(&mutex);
50+
51+
emscripten_out("Starting thread..");
52+
pthread_t thr;
53+
int s = pthread_create(&thr, NULL, thread_start, (void*)0);
54+
assert(s == 0);
55+
// Busy wait until thread is started
56+
while (!started) {
57+
sched_yield();
58+
}
59+
60+
emscripten_out("Canceling thread..");
61+
s = pthread_cancel(thr);
62+
assert(s == 0);
63+
// Busy wait until thread cancel handler has been run
64+
while (res != 1) {
65+
sched_yield();
66+
}
67+
68+
emscripten_out("Joining thread..");
69+
s = pthread_join(thr, NULL);
70+
assert(s == 0);
71+
emscripten_out("done");
72+
return 0;
73+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Starting thread..
2+
Thread started!
3+
Canceling thread..
4+
Called clean-up handler with arg 42
5+
Joining thread..
6+
done

test/test_core.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2691,6 +2691,14 @@ def test_atexit_threads(self):
26912691
self.set_setting('EXIT_RUNTIME')
26922692
self.do_core_test('test_atexit_threads.cpp')
26932693

2694+
@node_pthreads
2695+
def test_pthread_cancel(self):
2696+
self.do_run_in_out_file_test('pthread/test_pthread_cancel.cpp')
2697+
2698+
@node_pthreads
2699+
def test_pthread_cancel_async(self):
2700+
self.do_run_in_out_file_test('pthread/test_pthread_cancel_async.c')
2701+
26942702
@no_asan('test relies on null pointer reads')
26952703
def test_pthread_specific(self):
26962704
self.do_run_in_out_file_test('pthread/specific.c')

0 commit comments

Comments
 (0)