Skip to content

Commit

Permalink
Write more tests for signal handling
Browse files Browse the repository at this point in the history
There's now a much stronger level of assurance that signaling on Windows
will be atomic, low-latency, low tail latency, and shall never deadlock.
  • Loading branch information
jart committed Sep 21, 2024
1 parent 0e59afb commit dd8c4db
Show file tree
Hide file tree
Showing 19 changed files with 407 additions and 75 deletions.
2 changes: 1 addition & 1 deletion libc/calls/clock_nanosleep-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ static textwindows int sys_clock_nanosleep_nt_impl(int clock,
return 0;
msdelay = timespec_tomillis(timespec_sub(abs, now));
msdelay = MIN(msdelay, -1u);
if (_park_norestart(msdelay, waitmask))
if (_park_norestart(msdelay, waitmask) == -1)
return -1;
}
}
Expand Down
44 changes: 37 additions & 7 deletions libc/calls/park.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/events.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#ifdef __x86_64__

Expand All @@ -28,17 +36,39 @@
// raises ECANCELED if this POSIX thread was canceled in masked mode
textwindows static int _park_thread(uint32_t msdelay, sigset_t waitmask,
bool restartable) {
if (__sigcheck(waitmask, restartable) == -1)
return -1;
int expect = 0;
atomic_int futex = 0;
struct PosixThread *pt = _pthread_self();

// perform the wait operation
intptr_t sigev;
if (!(sigev = CreateEvent(0, 0, 0, 0)))
return __winerr();
pt->pt_event = sigev;
pt->pt_blkmask = waitmask;
atomic_store_explicit(&pt->pt_blocker, &futex, memory_order_release);
bool32 ok = WaitOnAddress(&futex, &expect, sizeof(int), msdelay);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_EVENT,
memory_order_release);
//!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!//
int sig = 0;
uint32_t ws = 0;
if (!_is_canceled() &&
!(_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))))
ws = WaitForSingleObject(sigev, msdelay);
//!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!/!//
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
if (ok && __sigcheck(waitmask, restartable) == -1)
CloseHandle(sigev);

// recursion is now safe
if (ws == -1)
return __winerr();
int handler_was_called = 0;
if (sig)
handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
if (_check_cancel())
return -1;
if (handler_was_called & SIG_HANDLED_NO_RESTART)
return eintr();
if (handler_was_called & SIG_HANDLED_SA_RESTART)
if (!restartable)
return eintr();
return 0;
}

Expand Down
8 changes: 8 additions & 0 deletions libc/calls/pause-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#ifdef __x86_64__

textwindows int sys_pause_nt(void) {
int rc;
// we don't strictly need to block signals, but it reduces signal
// delivery latency, by preventing other threads from delivering a
// signal asynchronously. it takes about ~5us to deliver a signal
// using SetEvent() whereas it takes ~30us to use SuspendThread(),
// GetThreadContext(), SetThreadContext(), and ResumeThread().
BLOCK_SIGNALS;
while (!(rc = _park_norestart(-1u, 0)))
donothing;
ALLOW_SIGNALS;
return rc;
}

Expand Down
88 changes: 46 additions & 42 deletions libc/calls/sig.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/ucontext.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/ucontext.h"
#include "libc/dce.h"
#include "libc/errno.h"
Expand Down Expand Up @@ -135,7 +136,24 @@ static textwindows wontreturn void __sig_terminate(int sig) {
TerminateThisProcess(sig);
}

static textwindows bool __sig_start(struct PosixThread *pt, int sig,
textwindows static void __sig_wake(struct PosixThread *pt, int sig) {
atomic_int *blocker;
blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
if (!blocker)
return;
// threads can create semaphores on an as-needed basis
if (blocker == PT_BLOCKER_EVENT) {
STRACE("%G set %d's event object", sig, _pthread_tid(pt));
SetEvent(pt->pt_event);
return;
}
// all other blocking ops that aren't overlap should use futexes
// we force restartable futexes to churn by waking w/o releasing
STRACE("%G waking %d's futex", sig, _pthread_tid(pt));
WakeByAddressSingle(blocker);
}

textwindows static bool __sig_start(struct PosixThread *pt, int sig,
unsigned *rva, unsigned *flags) {
*rva = __sighandrvas[sig];
*flags = __sighandflags[sig];
Expand All @@ -149,6 +167,7 @@ static textwindows bool __sig_start(struct PosixThread *pt, int sig,
STRACE("enqueing %G on %d", sig, _pthread_tid(pt));
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed);
__sig_wake(pt, sig);
return false;
}
if (*rva == (intptr_t)SIG_DFL) {
Expand All @@ -158,7 +177,7 @@ static textwindows bool __sig_start(struct PosixThread *pt, int sig,
return true;
}

static textwindows sigaction_f __sig_handler(unsigned rva) {
textwindows static sigaction_f __sig_handler(unsigned rva) {
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);
return (sigaction_f)(__executable_start + rva);
}
Expand Down Expand Up @@ -228,34 +247,15 @@ textwindows int __sig_relay(int sig, int sic, sigset_t waitmask) {
return handler_was_called;
}

// cancels blocking operations being performed by signaled thread
textwindows void __sig_cancel(struct PosixThread *pt, int sig, unsigned flags) {
atomic_int *blocker;
blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
if (!blocker) {
STRACE("%G sent to %d asynchronously", sig, _pthread_tid(pt));
return;
}
// threads can create semaphores on an as-needed basis
if (blocker == PT_BLOCKER_EVENT) {
STRACE("%G set %d's event object", sig, _pthread_tid(pt));
SetEvent(pt->pt_event);
return;
}
// all other blocking ops that aren't overlap should use futexes
// we force restartable futexes to churn by waking w/o releasing
STRACE("%G waking %d's futex", sig, _pthread_tid(pt));
WakeByAddressSingle(blocker);
}

// the user's signal handler callback is wrapped with this trampoline
static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
int sig = sf->si.si_signo;
struct CosmoTib *tib = __get_tls();
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
for (;;) {

// update the signal mask in preparation for signal handller
// update the signal mask in preparation for signal handler
sigset_t blocksigs = __sighandmask[sig];
if (!(sf->flags & SA_NODEFER))
blocksigs |= 1ull << (sig - 1);
Expand Down Expand Up @@ -302,12 +302,16 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
return 0;
}

// we can't preempt threads that masked sig or are blocked
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1))) {
// we can't preempt threads that masked sig or are blocked. we aso
// need to ensure we don't the target thread's stack if many signals
// need to be delivered at once. we also need to make sure two threads
// can't deadlock by killing each other at the same time.
if ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
(1ull << (sig - 1))) ||
atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire)) {
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed);
__sig_cancel(pt, sig, flags);
__sig_wake(pt, sig);
return 0;
}

Expand All @@ -321,28 +325,26 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
uintptr_t th = _pthread_syshand(pt);
if (atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire) &
(1ull << (sig - 1))) {
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return 0;
}

// take control of thread
// suspending the thread happens asynchronously
// however getting the context blocks until it's frozen
static pthread_spinlock_t killer_lock;
pthread_spin_lock(&killer_lock);
if (SuspendThread(th) == -1u) {
STRACE("SuspendThread failed w/ %d", GetLastError());
pthread_spin_unlock(&killer_lock);
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
struct NtContext nc;
nc.ContextFlags = kNtContextFull;
if (!GetThreadContext(th, &nc)) {
STRACE("GetThreadContext failed w/ %d", GetLastError());
ResumeThread(th);
pthread_spin_unlock(&killer_lock);
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
pthread_spin_unlock(&killer_lock);

// we can't preempt threads that masked sig or are blocked
// we can't preempt threads that are running in win32 code
Expand All @@ -354,7 +356,8 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed);
ResumeThread(th);
__sig_cancel(pt, sig, flags);
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
__sig_wake(pt, sig);
return 0;
}

Expand Down Expand Up @@ -387,10 +390,11 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
nc.Rsp = sp;
if (!SetThreadContext(th, &nc)) {
STRACE("SetThreadContext failed w/ %d", GetLastError());
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH;
}
ResumeThread(th);
__sig_cancel(pt, sig, flags);
__sig_wake(pt, sig);
return 0;
}

Expand All @@ -404,6 +408,7 @@ textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) {
}

// sends signal to any other thread
// this should only be called by non-posix threads
textwindows void __sig_generate(int sig, int sic) {
struct Dll *e;
struct PosixThread *pt, *mark = 0;
Expand Down Expand Up @@ -450,6 +455,7 @@ textwindows void __sig_generate(int sig, int sic) {
}
_pthread_unlock();
if (mark) {
// no lock needed since current thread is nameless and formless
__sig_killer(mark, sig, sic);
_pthread_unref(mark);
} else {
Expand Down Expand Up @@ -610,13 +616,11 @@ __msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) {
// didn't have the SA_RESTART flag, and `2`, which means SA_RESTART
// handlers were called (or `3` if both were the case).
textwindows int __sig_check(void) {
int sig;
if ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask,
memory_order_acquire)))) {
return __sig_raise(sig, SI_KERNEL);
} else {
return 0;
}
int sig, res = 0;
while ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask,
memory_order_acquire))))
res |= __sig_raise(sig, SI_KERNEL);
return res;
}

// background thread for delivering inter-process signals asynchronously
Expand All @@ -642,7 +646,7 @@ textwindows dontinstrument static uint32_t __sig_worker(void *arg) {
sigs &= ~(1ull << (sig - 1));
__sig_generate(sig, SI_KERNEL);
}
Sleep(1);
Sleep(POLL_INTERVAL_MS);
}
return 0;
}
Expand Down
16 changes: 8 additions & 8 deletions libc/calls/sigcheck.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@
#include "libc/sysv/errfuns.h"

textwindows int __sigcheck(sigset_t waitmask, bool restartable) {
int sig, handler_was_called;
int sig, handler_was_called = 0;
if (_check_cancel() == -1)
return -1;
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
while (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
handler_was_called |= _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1)
return -1;
if (handler_was_called & SIG_HANDLED_NO_RESTART)
return eintr();
if (handler_was_called & SIG_HANDLED_SA_RESTART)
if (!restartable)
return eintr();
}
if (handler_was_called & SIG_HANDLED_NO_RESTART)
return eintr();
if (handler_was_called & SIG_HANDLED_SA_RESTART)
if (!restartable)
return eintr();
return 0;
}
7 changes: 7 additions & 0 deletions libc/calls/sigsuspend.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,15 @@ int sigsuspend(const sigset_t *ignore) {
} else {
sigset_t waitmask = ignore ? *ignore : 0;
if (IsWindows() || IsMetal()) {
// we don't strictly need to block signals, but it reduces signal
// delivery latency, by preventing other threads from delivering a
// signal asynchronously. it takes about ~5us to deliver a signal
// using SetEvent() whereas it takes ~30us to use SuspendThread(),
// GetThreadContext(), SetThreadContext(), and ResumeThread().
BLOCK_SIGNALS;
while (!(rc = _park_norestart(-1u, waitmask)))
donothing;
ALLOW_SIGNALS;
} else {
rc = sys_sigsuspend((uint64_t[2]){waitmask}, 8);
}
Expand Down
3 changes: 2 additions & 1 deletion libc/intrin/itoa16.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/fmt/internal.h"

__msabi textwindows char16_t *__itoa16(char16_t p[21], uint64_t x) {
__msabi textwindows dontinstrument char16_t *__itoa16(char16_t p[21],
uint64_t x) {
char t;
size_t a, b, i = 0;
do {
Expand Down
3 changes: 1 addition & 2 deletions libc/intrin/sig.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ void __sig_unblock(sigset_t m) {
if (IsWindows() || IsMetal()) {
if (__tls_enabled) {
atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release);
if (_weaken(__sig_check)) {
if (_weaken(__sig_check))
_weaken(__sig_check)();
}
}
} else {
sys_sigprocmask(SIG_SETMASK, &m, 0);
Expand Down
12 changes: 9 additions & 3 deletions libc/intrin/sigproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "libc/nt/enum/pageflags.h"
#include "libc/nt/files.h"
#include "libc/nt/memory.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/thunk/msabi.h"
#ifdef __x86_64__
Expand All @@ -42,11 +43,16 @@ __msabi extern typeof(CreateFileMapping) *const __imp_CreateFileMappingW;
__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx;
__msabi extern typeof(SetEndOfFile) *const __imp_SetEndOfFile;
__msabi extern typeof(SetFilePointer) *const __imp_SetFilePointer;
__msabi extern typeof(GetEnvironmentVariable)
*const __imp_GetEnvironmentVariableW;

__msabi textwindows char16_t *__sig_process_path(char16_t *path, uint32_t pid,
int create_directories) {
// Generates C:\ProgramData\cosmo\sig\x\y.pid like path
__msabi textwindows dontinstrument char16_t *__sig_process_path(
char16_t *path, uint32_t pid, int create_directories) {
char16_t buf[3];
char16_t *p = path;
*p++ = 'C'; // C:\ProgramData\cosmo\sig\x\y.pid
uint32_t vlen = __imp_GetEnvironmentVariableW(u"SYSTEMDRIVE", buf, 3);
*p++ = vlen == 2 ? buf[0] : 'C';
*p++ = ':';
*p++ = '\\';
*p++ = 'P';
Expand Down
Loading

0 comments on commit dd8c4db

Please sign in to comment.