Skip to content

Commit 6643dc8

Browse files
committed
Fix pending signals to be TLS
Also, add some testing for this. I also ran this test under native clang / glibc on my desktop and got the expected results.
1 parent 2cf3138 commit 6643dc8

File tree

7 files changed

+161
-13
lines changed

7 files changed

+161
-13
lines changed

system/lib/libc/emscripten_internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@
2222
#include <stdbool.h> // for `bool`
2323
#include <stdint.h> // for `intptr_t`
2424
#include <sys/types.h> // for `off_t`
25+
#include <threads.h> // for `thread_local`
2526
#include <time.h> // for `struct tm`
2627

2728
#ifdef __cplusplus
2829
extern "C" {
2930
#endif
3031

32+
// Pending signals for the current thread. This gets populated when a signal
33+
// is raised but its blocked by pthread_sigmask.
34+
extern thread_local sigset_t __sig_pending;
35+
3136
_Noreturn void _abort_js(void);
3237

3338
void setThrew(uintptr_t threw, int value);

system/lib/libc/pthread_sigmask.c

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#define SST_SIZE (_NSIG/8/sizeof(long))
1616

1717
static thread_local sigset_t __sig_mask;
18-
sigset_t __sig_pending;
18+
thread_local sigset_t __sig_pending;
1919

2020
static int siginvertset(sigset_t *dest, const sigset_t *src) {
2121
unsigned long i = 0, *d = (void*) dest, *s = (void*) src;
@@ -27,6 +27,15 @@ bool __sig_is_blocked(int sig) {
2727
return sigismember(&__sig_mask, sig);
2828
}
2929

30+
static void raise_pending_signals() {
31+
for (int sig = 0; sig < _NSIG; sig++) {
32+
if (sigismember(&__sig_pending, sig) && !sigismember(&__sig_mask, sig)) {
33+
sigdelset(&__sig_pending, sig);
34+
raise(sig);
35+
}
36+
}
37+
}
38+
3039
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict old) {
3140
if (old) {
3241
*old = __sig_mask;
@@ -53,14 +62,9 @@ int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict ol
5362
sigdelset(&__sig_mask, SIGKILL);
5463
sigdelset(&__sig_mask, SIGSTOP);
5564

56-
// Raise any pending signals that are now unblocked.
57-
for (int sig = 0; sig < _NSIG; sig++) {
58-
if (sigismember(&__sig_pending, sig) && !sigismember(&__sig_mask, sig)) {
59-
sigdelset(&__sig_pending, sig);
60-
raise(sig);
61-
}
62-
}
63-
65+
// Now that current mask has changed, raise any pending signals that
66+
// might now be unblocked.
67+
raise_pending_signals();
6468
return 0;
6569
}
6670

system/lib/libc/raise.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
#include "emscripten_internal.h"
1414

1515
extern struct sigaction __sig_actions[_NSIG];
16-
extern sigset_t __sig_pending;
1716

1817
bool __sig_is_blocked(int sig);
1918

system/lib/libc/sigtimedwait.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
#include "syscall.h"
1111
#include "libc.h"
1212

13-
extern sigset_t __sig_pending;
13+
#include "emscripten_internal.h"
1414

1515
int sigtimedwait(const sigset_t *restrict mask, siginfo_t *restrict si, const struct timespec *restrict timeout) {
1616
for (int sig = 0; sig < _NSIG; sig++) {
1717
if (sigismember(mask, sig) && sigismember(&__sig_pending, sig)) {
1818
if (si) {
19-
siginfo_t t = {0};
19+
siginfo_t t = {.si_signo = sig};
2020
*si = t;
2121
}
2222
sigdelset(&__sig_pending, sig);

test/pthread/test_pthread_kill.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ void setup_handler() {
4141
sigaction(SIGUSR1, &act, NULL);
4242
}
4343

44-
4544
void sleepms(long msecs) {
4645
usleep(msecs * 1000);
4746
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Copyright 2026 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
#include <pthread.h>
9+
#include <sys/types.h>
10+
#include <stdbool.h>
11+
#include <stdio.h>
12+
#include <stdlib.h>
13+
#include <assert.h>
14+
#include <unistd.h>
15+
#include <errno.h>
16+
#include <signal.h>
17+
18+
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
19+
pthread_cond_t started = PTHREAD_COND_INITIALIZER;
20+
pthread_cond_t unblock = PTHREAD_COND_INITIALIZER;
21+
pthread_cond_t pending = PTHREAD_COND_INITIALIZER;
22+
_Atomic bool got_sigterm = false;
23+
_Atomic bool is_pending = false;
24+
_Atomic bool unblock_signal = false;
25+
26+
pthread_t child_thread;
27+
28+
void signal_handler(int sig, siginfo_t * info, void * arg) {
29+
printf("signal_handler: sig=%d onthread=%d\n", sig, pthread_self() == child_thread);
30+
assert(sig == SIGTERM);
31+
assert(pthread_self() == child_thread);
32+
got_sigterm = true;
33+
}
34+
35+
void setup_handler() {
36+
struct sigaction act;
37+
sigemptyset(&act.sa_mask);
38+
act.sa_flags = SA_SIGINFO;
39+
act.sa_sigaction = signal_handler;
40+
sigaction(SIGTERM, &act, NULL);
41+
}
42+
43+
void sleepms(long msecs) {
44+
usleep(msecs * 1000);
45+
}
46+
47+
void *thread_start(void *arg) {
48+
sigset_t set;
49+
int ret;
50+
51+
// First we block all signals.
52+
sigfillset(&set);
53+
pthread_sigmask(SIG_BLOCK, &set, NULL);
54+
55+
// Now we signal that we are running
56+
pthread_mutex_lock(&lock);
57+
pthread_cond_signal(&started);
58+
pthread_mutex_unlock(&lock);
59+
60+
printf("Waiting for SIGTERM to becoming pending\n");
61+
62+
// Now loop until we see that SIGTERM is pending.
63+
while (1) {
64+
sigpending(&set);
65+
if (sigismember(&set, SIGTERM)) {
66+
printf("SIGTERM is now pending\n");
67+
pthread_mutex_lock(&lock);
68+
is_pending = true;
69+
pthread_cond_signal(&pending);
70+
pthread_mutex_unlock(&lock);
71+
break;
72+
}
73+
sleepms(1);
74+
}
75+
76+
assert(!got_sigterm);
77+
78+
pthread_mutex_lock(&lock);
79+
if (!unblock_signal) {
80+
pthread_cond_wait(&unblock, &lock);
81+
}
82+
pthread_mutex_unlock(&lock);
83+
84+
// Now unlock all signals andwe should recieve SIGTERM here.
85+
printf("Unblocking signals\n");
86+
sigfillset(&set);
87+
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
88+
89+
assert(got_sigterm);
90+
91+
return NULL;
92+
}
93+
94+
int main() {
95+
int ret;
96+
setup_handler();
97+
98+
pthread_mutex_lock(&lock);
99+
ret = pthread_create(&child_thread, NULL, thread_start, 0);
100+
assert(ret == 0);
101+
102+
// Wait until thread kicks in and sets the shared variable.
103+
pthread_cond_wait(&started, &lock);
104+
pthread_mutex_unlock(&lock);
105+
printf("thread has started, sending SIGTERM\n");
106+
107+
ret = pthread_kill(child_thread, SIGTERM);
108+
assert(ret == 0);
109+
printf("SIGTERM sent\n");
110+
111+
pthread_mutex_lock(&lock);
112+
if (!is_pending) {
113+
pthread_cond_wait(&pending, &lock);
114+
}
115+
116+
// Now the signal is pending on the child thread we block and unblock
117+
// all signals on the mains thread, which should be a no-op
118+
// We had a bug where pending signals were not stored in TLS which would
119+
// cause this to raise the pending signals erronously here.
120+
sigset_t set;
121+
sigfillset(&set);
122+
pthread_sigmask(SIG_BLOCK, &set, NULL);
123+
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
124+
125+
// Signal the child thread to unlock and recieve the signal
126+
unblock_signal = true;
127+
pthread_cond_signal(&unblock);
128+
pthread_mutex_unlock(&lock);
129+
130+
pthread_join(child_thread, NULL);
131+
printf("done\n");
132+
return 0;
133+
}

test/test_other.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13045,6 +13045,14 @@ def test_pthread_kill(self):
1304513045
def test_pthread_kill_self(self, args):
1304613046
self.do_runf('pthread/test_pthread_kill_self.c', 'main\n', assert_returncode=NON_ZERO, cflags=args)
1304713047

13048+
@requires_pthreads
13049+
@parameterized({
13050+
'': (['-sPTHREAD_POOL_SIZE=1'],),
13051+
'proxied': (['-sPROXY_TO_PTHREAD', '-sEXIT_RUNTIME'],),
13052+
})
13053+
def test_pthread_sigmask(self, args):
13054+
self.do_runf('pthread/test_pthread_sigmask.c', 'done\n', cflags=args)
13055+
1304813056
# Tests memory growth in pthreads mode, but still on the main thread.
1304913057
@requires_pthreads
1305013058
@parameterized({

0 commit comments

Comments
 (0)