-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Fixing thread exit and thread cancellation #10524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,6 +173,30 @@ var LibraryPThread = { | |
if (ENVIRONMENT_IS_PTHREAD && _pthread_self()) ___pthread_tsd_run_dtors(); | ||
}, | ||
|
||
runExitHandlersAndDeinitThread: function(tb, exitCode) { | ||
#if PTHREADS_PROFILING | ||
var profilerBlock = Atomics.load(HEAPU32, (tb + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2); | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2, 0); | ||
_free(profilerBlock); | ||
#endif | ||
|
||
// Disable all cancellation so that executing the cleanup handlers won't trigger another JS | ||
// canceled exception to be thrown. | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2, 1/*PTHREAD_CANCEL_DISABLE*/); | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.cancelasync }}} ) >> 2, 0/*PTHREAD_CANCEL_DEFERRED*/); | ||
PThread.runExitHandlers(); | ||
|
||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, exitCode); | ||
// When we publish this, the main thread is free to deallocate the thread object and we are done. | ||
// Therefore set _pthread_self = 0; above to 'release' the object in this worker thread. | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); // Mark the thread as no longer running. | ||
|
||
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads | ||
|
||
// Not hosting a pthread anymore in this worker, reset the info structures to null. | ||
__emscripten_thread_init(0, 0, 0); // Unregister the thread block also inside the asm.js scope. | ||
}, | ||
|
||
// Called when we are performing a pthread_exit(), either explicitly called | ||
// by programmer, or implicitly when leaving the thread main function. | ||
threadExit: function(exitCode) { | ||
|
@@ -181,24 +205,8 @@ var LibraryPThread = { | |
#if ASSERTIONS | ||
err('Pthread 0x' + tb.toString(16) + ' exited.'); | ||
#endif | ||
#if PTHREADS_PROFILING | ||
var profilerBlock = Atomics.load(HEAPU32, (tb + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2); | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.profilerBlock }}} ) >> 2, 0); | ||
_free(profilerBlock); | ||
#endif | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, exitCode); | ||
// When we publish this, the main thread is free to deallocate the thread object and we are done. | ||
// Therefore set _pthread_self = 0; above to 'release' the object in this worker thread. | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); | ||
|
||
// Disable all cancellation so that executing the cleanup handlers won't trigger another JS | ||
// canceled exception to be thrown. | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.canceldisable }}} ) >> 2, 1/*PTHREAD_CANCEL_DISABLE*/); | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.cancelasync }}} ) >> 2, 0/*PTHREAD_CANCEL_DEFERRED*/); | ||
PThread.runExitHandlers(); | ||
|
||
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); | ||
__emscripten_thread_init(0, 0, 0); // Unregister the thread block also inside the asm.js scope. | ||
PThread.runExitHandlersAndDeinitThread(tb, exitCode); | ||
|
||
if (ENVIRONMENT_IS_PTHREAD) { | ||
// Note: in theory we would like to return any offscreen canvases back to the main thread, | ||
// but if we ever fetched a rendering context for them that would not be valid, so we don't try. | ||
|
@@ -208,13 +216,7 @@ var LibraryPThread = { | |
}, | ||
|
||
threadCancel: function() { | ||
PThread.runExitHandlers(); | ||
var tb = _pthread_self(); | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadExitCode }}} ) >> 2, -1/*PTHREAD_CANCELED*/); | ||
Atomics.store(HEAPU32, (tb + {{{ C_STRUCTS.pthread.threadStatus }}} ) >> 2, 1); // Mark the thread as no longer running. | ||
_emscripten_futex_wake(tb + {{{ C_STRUCTS.pthread.threadStatus }}}, {{{ cDefine('INT_MAX') }}}); // wake all threads | ||
// Not hosting a pthread anymore in this worker, reset the info structures to null. | ||
__emscripten_thread_init(0, 0, 0); // Unregister the thread block also inside the asm.js scope. | ||
PThread.runExitHandlersAndDeinitThread(_pthread_self(), -1/*PTHREAD_CANCELED*/); | ||
postMessage({ 'cmd': 'cancelDone' }); | ||
}, | ||
|
||
|
@@ -494,9 +496,23 @@ var LibraryPThread = { | |
$cleanupThread: function(pthread_ptr) { | ||
if (ENVIRONMENT_IS_PTHREAD) throw 'Internal Error! cleanupThread() can only ever be called from main application thread!'; | ||
if (!pthread_ptr) throw 'Internal Error! Null pthread_ptr in cleanupThread!'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two lines should probably be in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hard to tell that, as |
||
{{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}}; | ||
var pthread = PThread.pthreads[pthread_ptr]; | ||
abujalski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// If pthread has been removed from this map this also means that pthread_ptr points | ||
// to already freed data. Such situation may occur in following circumstances: | ||
// 1. Joining cancelled thread - in such situation it may happen that pthread data will | ||
// already be removed by handling 'cancelDone' message. | ||
// 2. Joining thread from non-main browser thread (this also includes thread running main() | ||
// when compiled with `PROXY_TO_PTHREAD`) - in such situation it may happen that following | ||
// code flow occur (MB - Main Browser Thread, S1, S2 - Worker Threads): | ||
// S2: thread ends, 'exit' message is sent to MB | ||
// S1: calls pthread_join(S2), this causes: | ||
// a. S2 is marked as detached, | ||
// b. 'cleanupThread' message is sent to MB. | ||
// MB: handles 'exit' message, as thread is detached, so returnWorkerToPool() | ||
// is called and all thread related structs are freed/released. | ||
// MB: handles 'cleanupThread' message which calls this function. | ||
if (pthread) { | ||
{{{ makeSetValue('pthread_ptr', C_STRUCTS.pthread.self, 0, 'i32') }}}; | ||
var worker = pthread.worker; | ||
PThread.returnWorkerToPool(worker); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// Copyright 2021 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 <assert.h> | ||
#include <unistd.h> | ||
#include <errno.h> | ||
#include <emscripten.h> | ||
#include <emscripten/threading.h> | ||
|
||
pthread_barrier_t barrier; | ||
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; | ||
pthread_cond_t condvar = PTHREAD_COND_INITIALIZER; | ||
|
||
int th_cancelled = 0; | ||
|
||
volatile int res = 43; | ||
|
||
static void cleanup_handler(void *arg) { | ||
emscripten_log(EM_LOG_CONSOLE, "Called clean-up handler with arg %p", arg); | ||
int a = reinterpret_cast<int>(arg); | ||
res -= a; | ||
|
||
pthread_mutex_unlock(&mutex); | ||
pthread_barrier_wait(&barrier); | ||
} | ||
|
||
static void *thread_start(void *arg) { | ||
pthread_cleanup_push(cleanup_handler, (void*)42); | ||
emscripten_log(EM_LOG_CONSOLE, "Thread started!"); | ||
pthread_mutex_lock(&mutex); | ||
pthread_barrier_wait(&barrier); | ||
|
||
int ret = 0; | ||
do { | ||
emscripten_log(EM_LOG_CONSOLE, "Waiting on conditional variable"); | ||
ret = pthread_cond_wait(&condvar, &mutex); | ||
} while (ret == 0 && th_cancelled == 0); | ||
|
||
if (ret != 0) { | ||
emscripten_log(EM_LOG_CONSOLE, "Cond wait failed ret: %d", ret); | ||
} | ||
|
||
res = 1000; // Shouldn't ever reach here. | ||
pthread_cleanup_pop(0); | ||
|
||
pthread_mutex_unlock(&mutex); | ||
pthread_barrier_wait(&barrier); | ||
return NULL; | ||
} | ||
|
||
int main() { | ||
if (!emscripten_has_threading_support()) { | ||
printf("Skipped: Threading is not supported.\n"); | ||
return 1; | ||
} | ||
|
||
pthread_barrier_init(&barrier, nullptr, 2); | ||
|
||
pthread_t thr; | ||
int s = pthread_create(&thr, NULL, thread_start, (void*)0); | ||
assert(s == 0); | ||
emscripten_log(EM_LOG_CONSOLE, "Thread created"); | ||
|
||
pthread_barrier_wait(&barrier); | ||
|
||
// Lock mutex to ensure that thread is waiting | ||
pthread_mutex_lock(&mutex); | ||
|
||
emscripten_log(EM_LOG_CONSOLE, "Canceling thread.."); | ||
s = pthread_cancel(thr); | ||
assert(s == 0); | ||
th_cancelled = 1; | ||
pthread_mutex_unlock(&mutex); | ||
|
||
emscripten_log(EM_LOG_CONSOLE, "Main thread waitnig for side-thread"); | ||
pthread_barrier_wait(&barrier); | ||
pthread_barrier_destroy(&barrier); | ||
|
||
emscripten_log(EM_LOG_CONSOLE, "Test finished result: %d", res); | ||
return res; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm curious how if this new test is testing similar things to the newly enabled posixtestsuite tests? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually yes, this test is supposed to check similar scenario to one tested in pthread_cond_wait_2_3 test. Although in this test scenario itself is simplified compared to test from posixtestsuite. Main purpose of this test was to check cancelling thread waiting on cv scenario without adding posixtestsuite. I don't know if posixtestsuite is run by CI but if so then this test can be removed. Do you think it is better to keep this tests or remove it as redundant? |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8148,7 +8148,6 @@ def test_pthread_c11_threads(self): | |
self.set_setting('INITIAL_MEMORY', '64mb') | ||
self.do_run_in_out_file_test('tests', 'pthread', 'test_pthread_c11_threads.c') | ||
|
||
@no_asan('flakey errors that must be fixed, https://github.com/emscripten-core/emscripten/issues/12985') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you update the PR title and description including specifically how this change address this issue? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure no problem. I've updated them. Is my description clear and understandable? |
||
@node_pthreads | ||
def test_pthread_cxx_threads(self): | ||
self.set_setting('PROXY_TO_PTHREAD') | ||
|
Uh oh!
There was an error while loading. Please reload this page.