Skip to content
Open
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
5 changes: 5 additions & 0 deletions system/lib/libc/emscripten_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@
#include <stdbool.h> // for `bool`
#include <stdint.h> // for `intptr_t`
#include <sys/types.h> // for `off_t`
#include <threads.h> // for `thread_local`
#include <time.h> // 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);
Expand Down
22 changes: 13 additions & 9 deletions system/lib/libc/pthread_sigmask.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down
1 change: 0 additions & 1 deletion system/lib/libc/raise.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions system/lib/libc/sigtimedwait.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions test/codesize/test_codesize_cxx_lto.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
4 changes: 2 additions & 2 deletions test/codesize/test_codesize_hello_dylink_all.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 0 additions & 1 deletion test/pthread/test_pthread_kill.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ void setup_handler() {
sigaction(SIGUSR1, &act, NULL);
}


void sleepms(long msecs) {
usleep(msecs * 1000);
}
Expand Down
133 changes: 133 additions & 0 deletions test/pthread/test_pthread_sigmask.c
Original file line number Diff line number Diff line change
@@ -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 <pthread.h>
#include <sys/types.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

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;
}
8 changes: 8 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Loading