Skip to content

Commit 85c58be

Browse files
committed
Fix an async signal delivery flake on Windows
1 parent e4d6eb3 commit 85c58be

File tree

2 files changed

+107
-46
lines changed

2 files changed

+107
-46
lines changed

libc/calls/sig.c

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -136,21 +136,22 @@ static textwindows wontreturn void __sig_terminate(int sig) {
136136
TerminateThisProcess(sig);
137137
}
138138

139-
textwindows static void __sig_wake(struct PosixThread *pt, int sig) {
139+
textwindows static bool __sig_wake(struct PosixThread *pt, int sig) {
140140
atomic_int *blocker;
141141
blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
142142
if (!blocker)
143-
return;
143+
return false;
144144
// threads can create semaphores on an as-needed basis
145145
if (blocker == PT_BLOCKER_EVENT) {
146146
STRACE("%G set %d's event object", sig, _pthread_tid(pt));
147147
SetEvent(pt->pt_event);
148-
return;
148+
return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
149149
}
150150
// all other blocking ops that aren't overlap should use futexes
151151
// we force restartable futexes to churn by waking w/o releasing
152152
STRACE("%G waking %d's futex", sig, _pthread_tid(pt));
153153
WakeByAddressSingle(blocker);
154+
return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
154155
}
155156

156157
textwindows static bool __sig_start(struct PosixThread *pt, int sig,
@@ -302,17 +303,48 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
302303
return 0;
303304
}
304305

305-
// we can't preempt threads that masked sigs or are blocked. we also
306-
// need to ensure we don't overflow the target thread's stack if many
307-
// signals need to be delivered at once. we also need to make sure two
308-
// threads can't deadlock by killing each other at the same time.
309-
if ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
310-
(1ull << (sig - 1))) ||
311-
atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire)) {
312-
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
313-
memory_order_relaxed);
314-
__sig_wake(pt, sig);
315-
return 0;
306+
// we can't preempt threads that masked sigs or are blocked on i/o
307+
while ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
308+
(1ull << (sig - 1)))) {
309+
if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
310+
memory_order_acq_rel) &
311+
(1ull << (sig - 1)))
312+
// we believe signal was already enqueued
313+
return 0;
314+
if (__sig_wake(pt, sig))
315+
// we believe i/o routine will handle signal
316+
return 0;
317+
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
318+
(1ull << (sig - 1)))
319+
// we believe ALLOW_SIGNALS will handle signal
320+
return 0;
321+
if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending,
322+
~(1ull << (sig - 1)),
323+
memory_order_acq_rel) &
324+
(1ull << (sig - 1))))
325+
// we believe another thread sniped our signal
326+
return 0;
327+
break;
328+
}
329+
330+
// avoid race conditions and deadlocks with thread suspend process
331+
if (atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire)) {
332+
// we believe another thread is asynchronously waking the mark
333+
if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
334+
memory_order_acq_rel) &
335+
(1ull << (sig - 1)))
336+
// we believe our signal is already being delivered
337+
return 0;
338+
if (atomic_load_explicit(&pt->pt_intoff, memory_order_acquire) ||
339+
atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire))
340+
// we believe __sig_tramp will deliver our signal
341+
return 0;
342+
if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending,
343+
~(1ull << (sig - 1)),
344+
memory_order_acq_rel) &
345+
(1ull << (sig - 1))))
346+
// we believe another thread sniped our signal
347+
return 0;
316348
}
317349

318350
// if there's no handler then killing a thread kills the process
@@ -321,17 +353,10 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
321353
__sig_terminate(sig);
322354
}
323355

324-
// ignore signals already pending
325-
uintptr_t th = _pthread_syshand(pt);
326-
if (atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire) &
327-
(1ull << (sig - 1))) {
328-
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
329-
return 0;
330-
}
331-
332356
// take control of thread
333357
// suspending the thread happens asynchronously
334358
// however getting the context blocks until it's frozen
359+
uintptr_t th = _pthread_syshand(pt);
335360
if (SuspendThread(th) == -1u) {
336361
STRACE("SuspendThread failed w/ %d", GetLastError());
337362
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
@@ -349,9 +374,7 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
349374
// we can't preempt threads that masked sig or are blocked
350375
// we can't preempt threads that are running in win32 code
351376
// so we shall unblock the thread and let it signal itself
352-
if ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
353-
(1ull << (sig - 1))) ||
354-
!((uintptr_t)__executable_start <= nc.Rip &&
377+
if (!((uintptr_t)__executable_start <= nc.Rip &&
355378
nc.Rip < (uintptr_t)__privileged_start)) {
356379
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
357380
memory_order_relaxed);
@@ -634,6 +657,7 @@ textwindows dontinstrument static uint32_t __sig_worker(void *arg) {
634657
__maps_track((char *)(((uintptr_t)sp + __pagesize - 1) & -__pagesize) - STKSZ,
635658
STKSZ);
636659
for (;;) {
660+
637661
// dequeue all pending signals and fire them off. if there's no
638662
// thread that can handle them then __sig_generate will requeue
639663
// those signals back to __sig.process; hence the need for xchg
@@ -644,6 +668,39 @@ textwindows dontinstrument static uint32_t __sig_worker(void *arg) {
644668
sigs &= ~(1ull << (sig - 1));
645669
__sig_generate(sig, SI_KERNEL);
646670
}
671+
672+
// unblock stalled asynchronous signals in threads
673+
_pthread_lock();
674+
for (struct Dll *e = dll_first(_pthread_list); e;
675+
e = dll_next(_pthread_list, e)) {
676+
struct PosixThread *pt = POSIXTHREAD_CONTAINER(e);
677+
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >=
678+
kPosixThreadTerminated) {
679+
break;
680+
}
681+
sigset_t pending =
682+
atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire);
683+
sigset_t mask =
684+
atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire);
685+
if (pending & ~mask) {
686+
_pthread_ref(pt);
687+
_pthread_unlock();
688+
while (!atomic_compare_exchange_weak_explicit(
689+
&pt->tib->tib_sigpending, &pending, pending & ~mask,
690+
memory_order_acq_rel, memory_order_relaxed)) {
691+
}
692+
while ((pending = pending & ~mask)) {
693+
int sig = bsfl(pending) + 1;
694+
pending &= ~(1ull << (sig - 1));
695+
__sig_killer(pt, sig, SI_KERNEL);
696+
}
697+
_pthread_lock();
698+
_pthread_unref(pt);
699+
}
700+
}
701+
_pthread_unlock();
702+
703+
// wait until next scheduler quantum
647704
Sleep(POLL_INTERVAL_MS);
648705
}
649706
return 0;

test/posix/signal_latency_async_test.c

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,15 @@ pthread_t sender_thread;
2727
pthread_t receiver_thread;
2828
struct timespec send_time;
2929
atomic_int sender_got_signal;
30+
atomic_int receiver_got_signal;
3031
double latencies[ITERATIONS];
3132

3233
void sender_signal_handler(int signo) {
3334
sender_got_signal = 1;
3435
}
3536

3637
void receiver_signal_handler(int signo) {
37-
struct timespec receive_time;
38-
clock_gettime(CLOCK_MONOTONIC, &receive_time);
39-
40-
long sec_diff = receive_time.tv_sec - send_time.tv_sec;
41-
long nsec_diff = receive_time.tv_nsec - send_time.tv_nsec;
42-
double latency_ns = sec_diff * 1e9 + nsec_diff;
43-
44-
static int iteration = 0;
45-
if (iteration < ITERATIONS)
46-
latencies[iteration++] = latency_ns;
47-
48-
// Pong sender
49-
if (pthread_kill(sender_thread, SIGUSR2))
50-
exit(2);
51-
52-
// Exit if done
53-
if (iteration >= ITERATIONS)
54-
pthread_exit(0);
38+
receiver_got_signal = 1;
5539
}
5640

5741
void *sender_func(void *arg) {
@@ -84,9 +68,29 @@ void *sender_func(void *arg) {
8468
void *receiver_func(void *arg) {
8569

8670
// Wait for asynchronous signals
87-
volatile unsigned v = 0;
88-
for (;;)
89-
++v;
71+
for (;;) {
72+
if (atomic_exchange_explicit(&receiver_got_signal, 0,
73+
memory_order_acq_rel)) {
74+
struct timespec receive_time;
75+
clock_gettime(CLOCK_MONOTONIC, &receive_time);
76+
77+
long sec_diff = receive_time.tv_sec - send_time.tv_sec;
78+
long nsec_diff = receive_time.tv_nsec - send_time.tv_nsec;
79+
double latency_ns = sec_diff * 1e9 + nsec_diff;
80+
81+
static int iteration = 0;
82+
if (iteration < ITERATIONS)
83+
latencies[iteration++] = latency_ns;
84+
85+
// Pong sender
86+
if (pthread_kill(sender_thread, SIGUSR2))
87+
exit(2);
88+
89+
// Exit if done
90+
if (iteration >= ITERATIONS)
91+
pthread_exit(0);
92+
}
93+
}
9094

9195
return 0;
9296
}

0 commit comments

Comments
 (0)