diff --git a/system/lib/libc/emscripten_internal.h b/system/lib/libc/emscripten_internal.h index 15465864c4d7a..2ea5eab8c0f33 100644 --- a/system/lib/libc/emscripten_internal.h +++ b/system/lib/libc/emscripten_internal.h @@ -22,12 +22,17 @@ #include // for `bool` #include // for `intptr_t` #include // for `off_t` +#include // for `thread_local` #include // for `struct tm` #ifdef __cplusplus extern "C" { #endif +// Pending signals for the current thread. This gets populated when a signal +// is raised but its blocked by pthread_sigmask. +extern thread_local sigset_t __sig_pending; + _Noreturn void _abort_js(void); void setThrew(uintptr_t threw, int value); diff --git a/system/lib/libc/pthread_sigmask.c b/system/lib/libc/pthread_sigmask.c index f0f1373712363..a4826dda3bd8b 100644 --- a/system/lib/libc/pthread_sigmask.c +++ b/system/lib/libc/pthread_sigmask.c @@ -15,7 +15,7 @@ #define SST_SIZE (_NSIG/8/sizeof(long)) static thread_local sigset_t __sig_mask; -sigset_t __sig_pending; +thread_local sigset_t __sig_pending; static int siginvertset(sigset_t *dest, const sigset_t *src) { unsigned long i = 0, *d = (void*) dest, *s = (void*) src; @@ -27,6 +27,15 @@ bool __sig_is_blocked(int sig) { return sigismember(&__sig_mask, sig); } +static void raise_pending_signals() { + for (int sig = 0; sig < _NSIG; sig++) { + if (sigismember(&__sig_pending, sig) && !sigismember(&__sig_mask, sig)) { + sigdelset(&__sig_pending, sig); + raise(sig); + } + } +} + int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict old) { if (old) { *old = __sig_mask; @@ -53,14 +62,9 @@ int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict ol sigdelset(&__sig_mask, SIGKILL); sigdelset(&__sig_mask, SIGSTOP); - // Raise any pending signals that are now unblocked. - for (int sig = 0; sig < _NSIG; sig++) { - if (sigismember(&__sig_pending, sig) && !sigismember(&__sig_mask, sig)) { - sigdelset(&__sig_pending, sig); - raise(sig); - } - } - + // Now that current mask has changed, raise any pending signals that + // might now be unblocked. + raise_pending_signals(); return 0; } diff --git a/system/lib/libc/raise.c b/system/lib/libc/raise.c index b653bf72c09d3..321d04f881881 100644 --- a/system/lib/libc/raise.c +++ b/system/lib/libc/raise.c @@ -13,7 +13,6 @@ #include "emscripten_internal.h" extern struct sigaction __sig_actions[_NSIG]; -extern sigset_t __sig_pending; bool __sig_is_blocked(int sig); diff --git a/system/lib/libc/sigtimedwait.c b/system/lib/libc/sigtimedwait.c index bccedd87323ed..ea7f719b8b6e9 100644 --- a/system/lib/libc/sigtimedwait.c +++ b/system/lib/libc/sigtimedwait.c @@ -10,13 +10,13 @@ #include "syscall.h" #include "libc.h" -extern sigset_t __sig_pending; +#include "emscripten_internal.h" int sigtimedwait(const sigset_t *restrict mask, siginfo_t *restrict si, const struct timespec *restrict timeout) { for (int sig = 0; sig < _NSIG; sig++) { if (sigismember(mask, sig) && sigismember(&__sig_pending, sig)) { if (si) { - siginfo_t t = {0}; + siginfo_t t = {.si_signo = sig}; *si = t; } sigdelset(&__sig_pending, sig); diff --git a/test/codesize/test_codesize_cxx_lto.json b/test/codesize/test_codesize_cxx_lto.json index 6c07b8cbdbf3f..4c2da355a7ebc 100644 --- a/test/codesize/test_codesize_cxx_lto.json +++ b/test/codesize/test_codesize_cxx_lto.json @@ -2,9 +2,9 @@ "a.out.js": 18563, "a.out.js.gz": 7666, "a.out.nodebug.wasm": 102164, - "a.out.nodebug.wasm.gz": 39553, + "a.out.nodebug.wasm.gz": 39554, "total": 120727, - "total_gz": 47219, + "total_gz": 47220, "sent": [ "a (emscripten_resize_heap)", "b (_setitimer_js)", diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index 105e4113624d7..fa899d92f26d1 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { "a.out.js": 244343, - "a.out.nodebug.wasm": 577473, - "total": 821816, + "a.out.nodebug.wasm": 577478, + "total": 821821, "sent": [ "IMG_Init", "IMG_Load", diff --git a/test/pthread/test_pthread_kill.c b/test/pthread/test_pthread_kill.c index cde94049f8994..b2b2b50a3d420 100644 --- a/test/pthread/test_pthread_kill.c +++ b/test/pthread/test_pthread_kill.c @@ -41,7 +41,6 @@ void setup_handler() { sigaction(SIGUSR1, &act, NULL); } - void sleepms(long msecs) { usleep(msecs * 1000); } diff --git a/test/pthread/test_pthread_sigmask.c b/test/pthread/test_pthread_sigmask.c new file mode 100644 index 0000000000000..25a336e1512c3 --- /dev/null +++ b/test/pthread/test_pthread_sigmask.c @@ -0,0 +1,133 @@ +/** + * Copyright 2026 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 +#include +#include +#include +#include +#include +#include +#include +#include + +pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t started = PTHREAD_COND_INITIALIZER; +pthread_cond_t unblock = PTHREAD_COND_INITIALIZER; +pthread_cond_t pending = PTHREAD_COND_INITIALIZER; +_Atomic bool got_sigterm = false; +_Atomic bool is_pending = false; +_Atomic bool unblock_signal = false; + +pthread_t child_thread; + +void signal_handler(int sig, siginfo_t * info, void * arg) { + printf("signal_handler: sig=%d onthread=%d\n", sig, pthread_self() == child_thread); + assert(sig == SIGTERM); + assert(pthread_self() == child_thread); + got_sigterm = true; +} + +void setup_handler() { + struct sigaction act; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = signal_handler; + sigaction(SIGTERM, &act, NULL); +} + +void sleepms(long msecs) { + usleep(msecs * 1000); +} + +void *thread_start(void *arg) { + sigset_t set; + int ret; + + // First we block all signals. + sigfillset(&set); + pthread_sigmask(SIG_BLOCK, &set, NULL); + + // Now we signal that we are running + pthread_mutex_lock(&lock); + pthread_cond_signal(&started); + pthread_mutex_unlock(&lock); + + printf("Waiting for SIGTERM to becoming pending\n"); + + // Now loop until we see that SIGTERM is pending. + while (1) { + sigpending(&set); + if (sigismember(&set, SIGTERM)) { + printf("SIGTERM is now pending\n"); + pthread_mutex_lock(&lock); + is_pending = true; + pthread_cond_signal(&pending); + pthread_mutex_unlock(&lock); + break; + } + sleepms(1); + } + + assert(!got_sigterm); + + pthread_mutex_lock(&lock); + if (!unblock_signal) { + pthread_cond_wait(&unblock, &lock); + } + pthread_mutex_unlock(&lock); + + // Now unlock all signals andwe should recieve SIGTERM here. + printf("Unblocking signals\n"); + sigfillset(&set); + pthread_sigmask(SIG_UNBLOCK, &set, NULL); + + assert(got_sigterm); + + return NULL; +} + +int main() { + int ret; + setup_handler(); + + pthread_mutex_lock(&lock); + ret = pthread_create(&child_thread, NULL, thread_start, 0); + assert(ret == 0); + + // Wait until thread kicks in and sets the shared variable. + pthread_cond_wait(&started, &lock); + pthread_mutex_unlock(&lock); + printf("thread has started, sending SIGTERM\n"); + + ret = pthread_kill(child_thread, SIGTERM); + assert(ret == 0); + printf("SIGTERM sent\n"); + + pthread_mutex_lock(&lock); + if (!is_pending) { + pthread_cond_wait(&pending, &lock); + } + + // Now the signal is pending on the child thread we block and unblock + // all signals on the mains thread, which should be a no-op + // We had a bug where pending signals were not stored in TLS which would + // cause this to raise the pending signals erronously here. + sigset_t set; + sigfillset(&set); + pthread_sigmask(SIG_BLOCK, &set, NULL); + pthread_sigmask(SIG_UNBLOCK, &set, NULL); + + // Signal the child thread to unlock and recieve the signal + unblock_signal = true; + pthread_cond_signal(&unblock); + pthread_mutex_unlock(&lock); + + pthread_join(child_thread, NULL); + printf("done\n"); + return 0; +} diff --git a/test/test_other.py b/test/test_other.py index de22e2d3594a9..4425eb9c14f12 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13045,6 +13045,14 @@ def test_pthread_kill(self): def test_pthread_kill_self(self, args): self.do_runf('pthread/test_pthread_kill_self.c', 'main\n', assert_returncode=NON_ZERO, cflags=args) + @requires_pthreads + @parameterized({ + '': (['-sPTHREAD_POOL_SIZE=1'],), + 'proxied': (['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],), + }) + def test_pthread_sigmask(self, args): + self.do_runf('pthread/test_pthread_sigmask.c', 'done\n', cflags=args) + # Tests memory growth in pthreads mode, but still on the main thread. @requires_pthreads @parameterized({