-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Run atexit handlers before terminating all threads #14481
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The primary change (moving terminateAllThreads) lgtm!
system/lib/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
Outdated
Show resolved
Hide resolved
@RReverser what do you think about the lsan change here? |
I would have though that lsan would register it own exit handler or somehow.. |
I don't understand its implications tbh. |
d19a715
to
94b61a9
Compare
src/postamble_minimal.js
Outdated
#endif | ||
#if USE_PTHREADS | ||
PThread.terminateAllThreads(); | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the per-thread exist handler should go up before the program-wide exit handlers.. (so up on line 25). If you do that perhaps we can avoid the lsan changes? I'm not sure.
So maybe the overall order should be:
- per-thread-exit handler for this thread (
PThread.runExitHandlers();
) - kill all other threads (PThread.terminateAllThreads())
- program-wide exit handlercallRuntimeCallbacks(ATEXIT);s (callRuntimeCallbacks(ATEXIT);
- program-wide JS exit handlers (<<< ATEXITS >>>)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PThread.terminateAllThreads()
(2) needs to be run after the program-wide exit handlers (3) and possibly also after the program-wide JS exit handlers (4). To test this, see the added test_pthread_atexit
in this PR which deadlocks on the main branch.
I've revised the order to:
- program-wide exit handlers (
callRuntimeCallbacks(__ATEXIT__)
). - program-wide JS exit handlers (
<<< ATEXITS >>>
). - terminate all other threads (
PThread.terminateAllThreads()
). - call exit handlers for the main thread (
PThread.runExitHandlers();
).
(this could probably be removed after PR Move__cxa_thread_atexit
to native code #14489, but I'm not sure)
I've reverted the LSan changes for now, and just suppressed _emscripten_builtin_pthread_create
from being leaky with commit 95e23fa.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PThread.terminateAllThreads()
(2) needs to be run after the program-wide exit handlers (3) and possibly also after the program-wide JS exit handlers (4). To test this, see the addedtest_pthread_atexit
in this PR which deadlocks on the main branch.
I still need to take a look at that test but this seems logically incorrect to me. The program wide exit handler run, for example, the C++ static destructors. If we don't kill all the threads before running these then we could have situation where there are threads still running with access to static C++ objects which have been destroyed. For example imagine std::cout has a static destructor and one of the background threads tries to write to cout during this time?
I've revised the order to:
- program-wide exit handlers (
callRuntimeCallbacks(__ATEXIT__)
).- program-wide JS exit handlers (
<<< ATEXITS >>>
).- terminate all other threads (
PThread.terminateAllThreads()
).- call exit handlers for the main thread (
PThread.runExitHandlers();
).
(this could probably be removed after PR Move__cxa_thread_atexit
to native code #14489, but I'm not sure)I've reverted the LSan changes for now, and just suppressed
_emscripten_builtin_pthread_create
from being leaky with commit 95e23fa.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think maybe you are right. Looking at the musl code for exit() is currently does:
_Noreturn void exit(int code)
{
__funcs_on_exit();
__libc_exit_fini();
__stdio_exit();
_Exit(code)
}
I think that secondary threads are not killed until _Exit
is called (which calls __syscall(SYS_exit_group, ec)
which I think terminates all the threads).
I guess that static destruction can run while there still threads running.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup .. i ran quick test :
class Foo {
public:
Foo() {
printf("Foo\n");
}
~Foo() {
printf("~Foo\n");
alive = false;
}
bool alive = true;
};
static Foo foo;
static _Atomic int threadCounter = 0;
static _Atomic int running = 1;
static pthread_t thread;
void *workerThread(void* arg) {
threadCounter++;
while (running) {
printf("foo: %d\n", foo.alive);
}
threadCounter--;
return NULL;
}
void mainAtExit() {
printf("mainAtExit\n");
sleep(1);
}
int main(int argc, char* argv[]) {
int rc = atexit(mainAtExit);
assert(rc == 0);
rc = pthread_create(&thread, NULL, workerThread, NULL);
assert(rc == 0);
return 0;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you are correct terminateAllThreads should come last.. and threads that are still alive that point might have a bad time but that is to be expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the investigation. Given that PThread.terminateAllThreads()
isn't last now, do you think PThread.runExitHandlers()
should occur before PThread.terminateAllThreads()
(i.e. swapping 3 and 4 in the above order)?
Also, let me know if you prefer commit 5805465 as a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would make a good separate PR I think. IIUC that is not a file that exists upstream right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
terminateAllThreads()
should come last of all I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
asan_emscripten.cpp
does indeed not exist upstream. I've moved that commit to PR #14516, and moved PThread.terminateAllThreads()
to occur as last.
I just uploaded #14512 which I'm pretty sure overlaps with this change. I can wait for this to land first if you prefer. |
No worries, feel free to land that PR earlier. |
PTAL, I've rebased it now that PR #14512 has landed. Note that commit 6ecd0d3 somehow didn't work, see:
|
... I've split the ASan/LSan related changes into separate PRs. |
b6fe214
to
8640757
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm with a few comments.
Thanks again for all your work on this!
src/library_pthread.js
Outdated
// ready for such calls, as this can be called after the sanitizers | ||
// are finalized. Instead, call free directly. | ||
withBuiltinMalloc(function () { | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other places that we use withBuiltinMalloc
are not guarded with if
like this. Instead withBuiltinMalloc
becomes a no-op. So I think you can avoid the if
checks here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that initially, but got this exception when I compiled without sanitizers (for e.g. when running wasm2.test_pthread_atexit
).
exception thrown: ReferenceError: withBuiltinMalloc is not defined
src/library_pthread.js
Outdated
@@ -154,7 +154,18 @@ var LibraryPThread = { | |||
} | |||
PThread.runningWorkers = []; | |||
}, | |||
#if USE_ASAN || USE_LSAN | |||
freeThreadData__deps: ['$withBuiltinMalloc'], | |||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an essential part of this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean this __deps
line? Or the withBuiltinMalloc
in general? The comment above withBuiltinMalloc
should explain why that is necessary, but perhaps it can be elaborated on further.
(Perhaps this could be moved to native code and mark that function with the no_sanitize("address")
attribute(?))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The __deps line is necessary, but none of the conditionals should be needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I had to move the $withBuiltinMalloc
to $PThread__deps
instead, otherwise it caused this exception when building without sanitizers:
exception thrown: ReferenceError: withBuiltinMalloc is not defined
tests/pthread/test_pthread_atexit.c
Outdated
|
||
static _Atomic int threadCounter = 0; | ||
static _Atomic int running = 1; | ||
static pthread_t thread; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably be consistent and either drop the static
here or add it to all he functions below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dropped those static
variables with commit 786473b.
tests/pthread/test_pthread_atexit.c
Outdated
threadCounter++; | ||
|
||
while (running) | ||
emscripten_thread_sleep(1000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why 1000 here? If we want to test to run as fast as possible should we use a shorter value? Or maybe pthread_cond_wait instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally this test case was based on the example from https://www.luke1410.de/blog/2017/02/the-trouble-of-separate-module-atexit-stacks/. But indeed, this can be adjusted by using pthread_cond_wait
instead, which is much neater.
Done with commit 786473b.
@@ -2322,6 +2322,12 @@ def test_pthread_equal(self): | |||
def test_pthread_dispatch_after_exit(self): | |||
self.do_run_in_out_file_test('pthread/test_pthread_dispatch_after_exit.c', interleaved_output=False) | |||
|
|||
@node_pthreads | |||
def test_pthread_atexit(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a comment either here or on the C source file describing exactly what we are testing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea! Done with commit 786473b.
The diff is now probably best viewable with the |
src/library_pthread.js
Outdated
// sanitizer code that checks some things about pthreads. We are not | ||
// ready for such calls, as this can be called after the sanitizers | ||
// are finalized. Instead, call free directly. | ||
withBuiltinMalloc(function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I think we can can instead just replace the calls to _free
below with calls to _emscripten_builtin_free
.
The withBuiltinMalloc
is mostly useful for calling entire functions that need to operate in both builtin and non-builtin modes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with commit 3c41330.
Can you try running bunch of asan and lsan tests locally to be sure this isn't going to generate any leaks/failures. I suggest |
After applying this patch (which disables some LSan tests that never returns on the --- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -2323,6 +2323,7 @@ The current type of b is: 9
self.do_run_in_out_file_test('pthread/test_pthread_dispatch_after_exit.c', interleaved_output=False)
@node_pthreads
+ @no_lsan('never returns when using LSan')
def test_pthread_nested_work_queue(self):
self.set_setting('EXIT_RUNTIME')
self.set_setting('PTHREAD_POOL_SIZE', 1)
@@ -2336,6 +2337,7 @@ The current type of b is: 9
self.do_run_in_out_file_test('pthread/test_pthread_thread_local_storage.cpp')
@node_pthreads
+ @no_lsan('never returns when using LSan')
def test_pthread_cleanup(self):
self.set_setting('EXIT_RUNTIME')
self.set_setting('PTHREAD_POOL_SIZE', 4)
@@ -8306,6 +8308,7 @@ NODEFS is no longer included by default; build with -lnodefs.js
self.do_run_in_out_file_test('pthread/test_pthread_c11_threads.c')
@node_pthreads
+ @no_lsan('never returns when using LSan')
def test_pthread_cxx_threads(self):
self.set_setting('PROXY_TO_PTHREAD')
self.clear_setting('ALLOW_MEMORY_GROWTH') I see this (tested commit 51dcc28): Details
And with this PR (tested commit 4eb2200) Details
So, it seems that |
With commit b477ef7, I now see: Details
Note that Line 2314 in c25daad
|
Oh sorry I didn't mean to send you on an lsan fixing adventure, I had assumed that lsan tests were previously passing but its only asan I guess that we have coverage for. I think this lsan work is good to have but maybe in separate PR? |
No problem, it would indeed be better to resolve that in a separate PR. I'll try to open a PR for that next week. I've reverted most of those changes for now, except for the change that regressed the |
Can we rebase this and get it landed? |
Although not defined by the POSIX standard, it seems that on most systems, atexit handlers are run before threads are terminated.
124726d
to
ab85ec9
Compare
Rebased as requested. Note that the Should I investigate that failure further? Or do you think we should revert that change for now (resulting in a regression for the |
src/postamble.js
Outdated
@@ -452,9 +449,6 @@ function exit(status, implicit) { | |||
function procExit(code) { | |||
EXITSTATUS = code; | |||
if (!keepRuntimeAlive()) { | |||
#if USE_PTHREADS | |||
PThread.terminateAllThreads(); | |||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this terminateAllThreads
is actually still useful for the case when _exit
or _Exit
is called with multiple threads running.
On node is probably doesn't matter since quit_t
will bring down the whole process, but on the web it will mean we don't leak workers in this case.
This will always happen after any atexit processing has occurred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it was redundant to call terminateAllThreads
here, since the only callers of procExit
are:
Line 449 in 565fb36
procExit(status); |
(which will be called just after
exitRuntime
, in case we don't need to keep the runtime alive)
And:
emscripten/src/library_wasi.js
Line 14 in cecc2fe
procExit(code); |
(which will be only called by
MINIMAL_RUNTIME
and STANDALONE_WASM
, AFAIK)
I added the terminateAllThreads
in postamble_minimal.js
to make sure that it still terminates all threads during exit
when linking with -sMINIMAL_RUNTIME
. But for STANDALONE_WASM
pthreads are not supported at all, so this seemed redundant.
Lines 2060 to 2061 in 565fb36
if settings.USE_PTHREADS: | |
exit_with_error('STANDALONE_WASM does not support pthreads yet') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it was redundant to call
terminateAllThreads
here, since the only callers ofprocExit
are:
Line 449 in 565fb36
procExit(status); (which will be called just after
exitRuntime
, in case we don't need to keep the runtime alive)
And:
emscripten/src/library_wasi.js
Line 14 in cecc2fe
procExit(code); (which will be only called by
MINIMAL_RUNTIME
andSTANDALONE_WASM
, AFAIK)
This callsite is used if the user calls _exit
or _Exit
. Its basically the exit syscall. Its not just used in STANDALONE_WASM IIUC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes. I missed this one:
__wasi_proc_exit(ec); |
(where
__wasi_proc_exit
is just an alias for proc_exit
)
Commit ea90c63 fixes this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this also seems to fix those asan.*thread*
failures without having to change _free
to _emscripten_builtin_free
in PThread.freeThreadData
. So, I've reverted that with commit 0f6f592.
tests/test_core.py
Outdated
@@ -2323,6 +2323,13 @@ def test_pthread_equal(self): | |||
def test_pthread_dispatch_after_exit(self): | |||
self.do_run_in_out_file_test('pthread/test_pthread_dispatch_after_exit.c', interleaved_output=False) | |||
|
|||
@node_pthreads | |||
def test_pthread_atexit(self): | |||
# Test whether we can terminate a running thread during atexit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about "Test that secondary threads are still running during atexit handlers"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've rephrased it as "Test to ensure threads are still running when atexit-registered functions are called" with commit ea90c63.
Yes, lets just revert that change and live with the lsan regression. It seems that we only currently run |
Oh.. I see the problem. When lsan we just link
This seems like a pre-existing condition that we don't need to fix in this PR. |
This is second attempt at landing a version of d5d5f69 The first time we tried it #13837 we ran into issues with test_pthread_exit_process deadlocking which I tracked down to an issue with `system/lib/libc/musl/src/thread/__wait.c` where it was blocking the main thread forever rather then looping and calling `emscripten_main_thread_process_queued_calls`. Includes a version of #14481 so that should land before this does. Fixes: #13194
The call to `PThread.terminateAllThreads()` in `procExit` that was reintroduced made this unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! So is this good to land now?
I left a couple of nits but otherwise looks good to me.
@@ -1 +1 @@ | |||
16368 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this file be reverted now without causes a test failure? (looks like its within the tolerance maybe?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems to be within the expected tolerance. I reverted it to its original state with commit 4f5be19.
tests/pthread/test_pthread_atexit.c
Outdated
pthread_mutex_lock(&count_lock); | ||
if (count == 0) | ||
pthread_cond_signal(&count_nonzero); | ||
count++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be simplified to just:
count = 1;
pthread_cond_signal(&count_nonzero);
I might even just rename int count
to bool should_exit
since we don't need to actually count anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, counting isn't necessary here (there are not multiple threads). Simplified with commit 4f5be19.
I think this is good now, feel free to land. |
Great to see this land, thanks @kleisauke ! |
This is second attempt at landing a version of d5d5f69 The first time we tried it #13837 we ran into issues with test_pthread_exit_process deadlocking which I tracked down to an issue with `system/lib/libc/musl/src/thread/__wait.c` where it was blocking the main thread forever rather then looping and calling `emscripten_main_thread_process_queued_calls`. Includes a version of #14481 so that should land before this does. Fixes: #13194
This is second attempt at landing a version of d5d5f69 The first time we tried it #13837 we ran into issues with test_pthread_exit_process deadlocking which I tracked down to an issue with `system/lib/libc/musl/src/thread/__wait.c` where it was blocking the main thread forever rather then looping and calling `emscripten_main_thread_process_queued_calls`. Includes a version of #14481 so that should land before this does. Fixes: #13194
This is second attempt at landing a version of d5d5f69 The first time we tried it #13837 we ran into issues with test_pthread_exit_process deadlocking which I tracked down to an issue with `system/lib/libc/musl/src/thread/__wait.c` where it was blocking the main thread forever rather then looping and calling `emscripten_main_thread_process_queued_calls`. Includes a version of #14481 so that should land before this does. Fixes: #13194
Although not defined by the POSIX standard, it seems that on most
systems1, atexit handlers are run before threads are terminated.
Helps: #13837.
Fixes: #14993.
1: AFAIK, only on Windows when linking against Microsoft's Universal
C runtime (UCRT), threads are terminated before atexit handlers are
called. See:
https://www.luke1410.de/blog/2017/02/the-trouble-of-separate-module-atexit-stacks/