Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions system/lib/libc/musl/src/thread/__timedwait.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,18 @@ int __timedwait_cp(volatile int *addr, int val,
// which may be either done by the user of __timedwait() function.
if (is_runtime_thread ||
pthread_self()->canceldisable != PTHREAD_CANCEL_DISABLE ||
pthread_self()->cancelasync == PTHREAD_CANCEL_ASYNCHRONOUS) {
pthread_self()->cancelasync) {
double sleepUntilTime = emscripten_get_now() + msecsToSleep;
do {
if (pthread_self()->cancel) {
// Emscripten-specific return value: The wait was canceled by user calling
// pthread_cancel() for this thread, and the caller needs to cooperatively
// cancel execution.
// The thread was canceled by pthread_cancel().
// In the case of cancelasync or PTHREAD_CANCEL_ENABLE we can just call
// __pthread_testcancel(), which won't return at all.
__pthread_testcancel();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the return below is never reached, right? It might be helpful to clarify that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, maybe it is reached if self->cancelasync is false and self->canceldisable is true? Does it make sense to return ECANCELED in that case?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently there are at least some tests that depend on that behaviour yes.

I tried completely removing the return ECANCELED; and these test fail:

posixtest.test_pthread_cond_wait_2_3
posixtest.test_pthread_cond_timedwait_2_6

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that reason for this. canceldisable can be in a third state called PTHREAD_CANCEL_MASKED which is used in pthread_cond_timedwait.c. The expectation here seems to be that __timedwait_cp will detect cancellation and return ECANCELLED, as we are doing here. Which is why this is needed.

// If __pthread_testcancel does return here it means that canceldisable
// must be set to PTHREAD_CANCEL_MASKED. This appear to mean "return
// ECANCELLED to the caller". See pthread_cond_timedwait.c for the only
// use of this that I could find.
return ECANCELED;
}
msecsToSleep = sleepUntilTime - emscripten_get_now();
Expand Down
11 changes: 11 additions & 0 deletions system/lib/libc/musl/src/thread/pthread_cancel.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ hidden long __cancel(), __syscall_cp_asm(), __syscall_cp_c();
long __cancel()
{
pthread_t self = __pthread_self();
#ifdef __EMSCRIPTEN__
// Emscripten doesn't have actual async cancelation so we make a best effort
// by cancelling cooperatively when self->cancelasync is set.
if (self->canceldisable == PTHREAD_CANCEL_ENABLE || self->cancelasync)
#else
if (self->canceldisable == PTHREAD_CANCEL_ENABLE)
#endif
pthread_exit(PTHREAD_CANCELED);
self->canceldisable = PTHREAD_CANCEL_DISABLE;
return -ECANCELED;
Expand Down Expand Up @@ -77,7 +83,12 @@ static void cancel_handler(int sig, siginfo_t *si, void *ctx)
void __testcancel()
{
pthread_t self = __pthread_self();
#ifdef __EMSCRIPTEN__
// See comment above about cancelasync under emscripten.
if (self->cancel && (self->cancelasync || !self->canceldisable))
#else
if (self->cancel && !self->canceldisable)
#endif
__cancel();
}

Expand Down
4 changes: 4 additions & 0 deletions test/pthread/test_pthread_cancel.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Canceling thread..
Thread started!
Called clean-up handler with arg 42
After canceling, shared variable = 1.
73 changes: 73 additions & 0 deletions test/pthread/test_pthread_cancel_async.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2015 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.

#include <pthread.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <emscripten/console.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
_Atomic long res = 43;
_Atomic int started = false;

static void cleanup_handler(void *arg)
{
long a = (long)arg;
emscripten_outf("Called clean-up handler with arg %ld", a);
res -= a;
}

static void *thread_start(void *arg) {
// Setup thread for async cancelation only
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

pthread_cleanup_push(cleanup_handler, (void*)42);

emscripten_out("Thread started!");

// Signal the main thread that are started
started = true;

// This mutex is locked by the main thread so this call should never return.
// pthread_mutex_lock is not a cancellation point so deferred cancellation
// won't work here, async cancelation should.
pthread_mutex_lock(&mutex);

assert(false && "pthread_mutex_lock returned!");
pthread_cleanup_pop(0);
}

int main() {
pthread_mutex_lock(&mutex);

emscripten_out("Starting thread..");
pthread_t thr;
int s = pthread_create(&thr, NULL, thread_start, (void*)0);
assert(s == 0);
// Busy wait until thread is started
while (!started) {
sched_yield();
}

emscripten_out("Canceling thread..");
s = pthread_cancel(thr);
assert(s == 0);
// Busy wait until thread cancel handler has been run
while (res != 1) {
sched_yield();
}

emscripten_out("Joining thread..");
s = pthread_join(thr, NULL);
assert(s == 0);
emscripten_out("done");
return 0;
}
6 changes: 6 additions & 0 deletions test/pthread/test_pthread_cancel_async.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Starting thread..
Thread started!
Canceling thread..
Called clean-up handler with arg 42
Joining thread..
done
8 changes: 8 additions & 0 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2691,6 +2691,14 @@ def test_atexit_threads(self):
self.set_setting('EXIT_RUNTIME')
self.do_core_test('test_atexit_threads.cpp')

@node_pthreads
def test_pthread_cancel(self):
self.do_run_in_out_file_test('pthread/test_pthread_cancel.cpp')
Comment thread
sbc100 marked this conversation as resolved.

@node_pthreads
def test_pthread_cancel_async(self):
self.do_run_in_out_file_test('pthread/test_pthread_cancel_async.c')

@no_asan('test relies on null pointer reads')
def test_pthread_specific(self):
self.do_run_in_out_file_test('pthread/specific.c')
Expand Down