Write more tests for signal handling

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.
This commit is contained in:
Justine Tunney 2024-09-21 05:24:56 -07:00
parent 0e59afb403
commit dd8c4dbd7d
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
19 changed files with 407 additions and 75 deletions

View file

@ -40,7 +40,7 @@ static textwindows int sys_clock_nanosleep_nt_impl(int clock,
return 0; return 0;
msdelay = timespec_tomillis(timespec_sub(abs, now)); msdelay = timespec_tomillis(timespec_sub(abs, now));
msdelay = MIN(msdelay, -1u); msdelay = MIN(msdelay, -1u);
if (_park_norestart(msdelay, waitmask)) if (_park_norestart(msdelay, waitmask) == -1)
return -1; return -1;
} }
} }

View file

@ -17,9 +17,17 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/atomic.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/nt/synchronization.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#ifdef __x86_64__ #ifdef __x86_64__
@ -28,17 +36,39 @@
// raises ECANCELED if this POSIX thread was canceled in masked mode // raises ECANCELED if this POSIX thread was canceled in masked mode
textwindows static int _park_thread(uint32_t msdelay, sigset_t waitmask, textwindows static int _park_thread(uint32_t msdelay, sigset_t waitmask,
bool restartable) { bool restartable) {
if (__sigcheck(waitmask, restartable) == -1)
return -1;
int expect = 0;
atomic_int futex = 0;
struct PosixThread *pt = _pthread_self(); 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; pt->pt_blkmask = waitmask;
atomic_store_explicit(&pt->pt_blocker, &futex, memory_order_release); atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_EVENT,
bool32 ok = WaitOnAddress(&futex, &expect, sizeof(int), msdelay); 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); 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; 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; return 0;
} }

View file

@ -17,13 +17,21 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/syscall_support-nt.internal.h"
#ifdef __x86_64__ #ifdef __x86_64__
textwindows int sys_pause_nt(void) { textwindows int sys_pause_nt(void) {
int rc; 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))) while (!(rc = _park_norestart(-1u, 0)))
donothing; donothing;
ALLOW_SIGNALS;
return rc; return rc;
} }

View file

@ -25,6 +25,7 @@
#include "libc/calls/struct/siginfo.h" #include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/ucontext.internal.h" #include "libc/calls/struct/ucontext.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/ucontext.h" #include "libc/calls/ucontext.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
@ -135,7 +136,24 @@ static textwindows wontreturn void __sig_terminate(int sig) {
TerminateThisProcess(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) { unsigned *rva, unsigned *flags) {
*rva = __sighandrvas[sig]; *rva = __sighandrvas[sig];
*flags = __sighandflags[sig]; *flags = __sighandflags[sig];
@ -149,6 +167,7 @@ static textwindows bool __sig_start(struct PosixThread *pt, int sig,
STRACE("enqueing %G on %d", sig, _pthread_tid(pt)); STRACE("enqueing %G on %d", sig, _pthread_tid(pt));
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1), atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed); memory_order_relaxed);
__sig_wake(pt, sig);
return false; return false;
} }
if (*rva == (intptr_t)SIG_DFL) { if (*rva == (intptr_t)SIG_DFL) {
@ -158,7 +177,7 @@ static textwindows bool __sig_start(struct PosixThread *pt, int sig,
return true; 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); atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);
return (sigaction_f)(__executable_start + rva); return (sigaction_f)(__executable_start + rva);
} }
@ -228,34 +247,15 @@ textwindows int __sig_relay(int sig, int sic, sigset_t waitmask) {
return handler_was_called; 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 // the user's signal handler callback is wrapped with this trampoline
static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) { static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
int sig = sf->si.si_signo; int sig = sf->si.si_signo;
struct CosmoTib *tib = __get_tls(); struct CosmoTib *tib = __get_tls();
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread; struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
for (;;) { for (;;) {
// update the signal mask in preparation for signal handller // update the signal mask in preparation for signal handler
sigset_t blocksigs = __sighandmask[sig]; sigset_t blocksigs = __sighandmask[sig];
if (!(sf->flags & SA_NODEFER)) if (!(sf->flags & SA_NODEFER))
blocksigs |= 1ull << (sig - 1); blocksigs |= 1ull << (sig - 1);
@ -302,12 +302,16 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
return 0; return 0;
} }
// we can't preempt threads that masked sig or are blocked // we can't preempt threads that masked sig or are blocked. we aso
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) & // need to ensure we don't the target thread's stack if many signals
(1ull << (sig - 1))) { // 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), atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed); memory_order_relaxed);
__sig_cancel(pt, sig, flags); __sig_wake(pt, sig);
return 0; return 0;
} }
@ -321,17 +325,16 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
uintptr_t th = _pthread_syshand(pt); uintptr_t th = _pthread_syshand(pt);
if (atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire) & if (atomic_load_explicit(&pt->tib->tib_sigpending, memory_order_acquire) &
(1ull << (sig - 1))) { (1ull << (sig - 1))) {
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return 0; return 0;
} }
// take control of thread // take control of thread
// suspending the thread happens asynchronously // suspending the thread happens asynchronously
// however getting the context blocks until it's frozen // however getting the context blocks until it's frozen
static pthread_spinlock_t killer_lock;
pthread_spin_lock(&killer_lock);
if (SuspendThread(th) == -1u) { if (SuspendThread(th) == -1u) {
STRACE("SuspendThread failed w/ %d", GetLastError()); STRACE("SuspendThread failed w/ %d", GetLastError());
pthread_spin_unlock(&killer_lock); atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH; return ESRCH;
} }
struct NtContext nc; struct NtContext nc;
@ -339,10 +342,9 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
if (!GetThreadContext(th, &nc)) { if (!GetThreadContext(th, &nc)) {
STRACE("GetThreadContext failed w/ %d", GetLastError()); STRACE("GetThreadContext failed w/ %d", GetLastError());
ResumeThread(th); ResumeThread(th);
pthread_spin_unlock(&killer_lock); atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH; return ESRCH;
} }
pthread_spin_unlock(&killer_lock);
// we can't preempt threads that masked sig or are blocked // we can't preempt threads that masked sig or are blocked
// we can't preempt threads that are running in win32 code // we can't preempt threads that are running in win32 code
@ -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), atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed); memory_order_relaxed);
ResumeThread(th); ResumeThread(th);
__sig_cancel(pt, sig, flags); atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
__sig_wake(pt, sig);
return 0; return 0;
} }
@ -387,10 +390,11 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
nc.Rsp = sp; nc.Rsp = sp;
if (!SetThreadContext(th, &nc)) { if (!SetThreadContext(th, &nc)) {
STRACE("SetThreadContext failed w/ %d", GetLastError()); STRACE("SetThreadContext failed w/ %d", GetLastError());
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
return ESRCH; return ESRCH;
} }
ResumeThread(th); ResumeThread(th);
__sig_cancel(pt, sig, flags); __sig_wake(pt, sig);
return 0; return 0;
} }
@ -404,6 +408,7 @@ textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) {
} }
// sends signal to any other thread // sends signal to any other thread
// this should only be called by non-posix threads
textwindows void __sig_generate(int sig, int sic) { textwindows void __sig_generate(int sig, int sic) {
struct Dll *e; struct Dll *e;
struct PosixThread *pt, *mark = 0; struct PosixThread *pt, *mark = 0;
@ -450,6 +455,7 @@ textwindows void __sig_generate(int sig, int sic) {
} }
_pthread_unlock(); _pthread_unlock();
if (mark) { if (mark) {
// no lock needed since current thread is nameless and formless
__sig_killer(mark, sig, sic); __sig_killer(mark, sig, sic);
_pthread_unref(mark); _pthread_unref(mark);
} else { } else {
@ -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 // didn't have the SA_RESTART flag, and `2`, which means SA_RESTART
// handlers were called (or `3` if both were the case). // handlers were called (or `3` if both were the case).
textwindows int __sig_check(void) { textwindows int __sig_check(void) {
int sig; int sig, res = 0;
if ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask, while ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask,
memory_order_acquire)))) { memory_order_acquire))))
return __sig_raise(sig, SI_KERNEL); res |= __sig_raise(sig, SI_KERNEL);
} else { return res;
return 0;
}
} }
// background thread for delivering inter-process signals asynchronously // background thread for delivering inter-process signals asynchronously
@ -642,7 +646,7 @@ textwindows dontinstrument static uint32_t __sig_worker(void *arg) {
sigs &= ~(1ull << (sig - 1)); sigs &= ~(1ull << (sig - 1));
__sig_generate(sig, SI_KERNEL); __sig_generate(sig, SI_KERNEL);
} }
Sleep(1); Sleep(POLL_INTERVAL_MS);
} }
return 0; return 0;
} }

View file

@ -23,18 +23,18 @@
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
textwindows int __sigcheck(sigset_t waitmask, bool restartable) { textwindows int __sigcheck(sigset_t waitmask, bool restartable) {
int sig, handler_was_called; int sig, handler_was_called = 0;
if (_check_cancel() == -1) if (_check_cancel() == -1)
return -1; return -1;
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) { while (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask); handler_was_called |= _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1) if (_check_cancel() == -1)
return -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; return 0;
} }

View file

@ -53,8 +53,15 @@ int sigsuspend(const sigset_t *ignore) {
} else { } else {
sigset_t waitmask = ignore ? *ignore : 0; sigset_t waitmask = ignore ? *ignore : 0;
if (IsWindows() || IsMetal()) { 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))) while (!(rc = _park_norestart(-1u, waitmask)))
donothing; donothing;
ALLOW_SIGNALS;
} else { } else {
rc = sys_sigsuspend((uint64_t[2]){waitmask}, 8); rc = sys_sigsuspend((uint64_t[2]){waitmask}, 8);
} }

View file

@ -18,7 +18,8 @@
*/ */
#include "libc/fmt/internal.h" #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; char t;
size_t a, b, i = 0; size_t a, b, i = 0;
do { do {

View file

@ -44,9 +44,8 @@ void __sig_unblock(sigset_t m) {
if (IsWindows() || IsMetal()) { if (IsWindows() || IsMetal()) {
if (__tls_enabled) { if (__tls_enabled) {
atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release); atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release);
if (_weaken(__sig_check)) { if (_weaken(__sig_check))
_weaken(__sig_check)(); _weaken(__sig_check)();
}
} }
} else { } else {
sys_sigprocmask(SIG_SETMASK, &m, 0); sys_sigprocmask(SIG_SETMASK, &m, 0);

View file

@ -29,6 +29,7 @@
#include "libc/nt/enum/pageflags.h" #include "libc/nt/enum/pageflags.h"
#include "libc/nt/files.h" #include "libc/nt/files.h"
#include "libc/nt/memory.h" #include "libc/nt/memory.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h" #include "libc/nt/runtime.h"
#include "libc/nt/thunk/msabi.h" #include "libc/nt/thunk/msabi.h"
#ifdef __x86_64__ #ifdef __x86_64__
@ -42,11 +43,16 @@ __msabi extern typeof(CreateFileMapping) *const __imp_CreateFileMappingW;
__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx; __msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx;
__msabi extern typeof(SetEndOfFile) *const __imp_SetEndOfFile; __msabi extern typeof(SetEndOfFile) *const __imp_SetEndOfFile;
__msabi extern typeof(SetFilePointer) *const __imp_SetFilePointer; __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, // Generates C:\ProgramData\cosmo\sig\x\y.pid like path
int create_directories) { __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; 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++ = '\\';
*p++ = 'P'; *p++ = 'P';

View file

@ -47,6 +47,9 @@ textwindows int __sig_mask(int how, const sigset_t *neu, sigset_t *old) {
} else { // SIG_SETMASK } else { // SIG_SETMASK
oldmask = atomic_exchange_explicit(mask, *neu, memory_order_acq_rel); oldmask = atomic_exchange_explicit(mask, *neu, memory_order_acq_rel);
} }
if (_weaken(__sig_check)) {
_weaken(__sig_check)();
}
} else { } else {
oldmask = atomic_load_explicit(mask, memory_order_acquire); oldmask = atomic_load_explicit(mask, memory_order_acquire);
} }
@ -56,10 +59,6 @@ textwindows int __sig_mask(int how, const sigset_t *neu, sigset_t *old) {
*old = oldmask; *old = oldmask;
} }
if (_weaken(__sig_check)) {
_weaken(__sig_check)();
}
return 0; return 0;
} }

View file

@ -246,6 +246,7 @@ static relegated void ShowCrashReport(int err, int sig, siginfo_t *si,
if (g_fds.n) if (g_fds.n)
kprintf("\n"); kprintf("\n");
__printfds(g_fds.p, g_fds.n); __printfds(g_fds.p, g_fds.n);
kprintf("\n");
if (__argv) if (__argv)
for (i = 0; i < __argc; ++i) for (i = 0; i < __argc; ++i)
kprintf("%s ", __argv[i]); kprintf("%s ", __argv[i]);

View file

@ -23,6 +23,7 @@
#include "libc/calls/struct/rusage.h" #include "libc/calls/struct/rusage.h"
#include "libc/calls/struct/siginfo.h" #include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/cosmo.h" #include "libc/cosmo.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/fmt/wintime.internal.h" #include "libc/fmt/wintime.internal.h"
@ -169,7 +170,7 @@ static textwindows dontinstrument uint32_t __proc_worker(void *arg) {
// wait for something to happen // wait for something to happen
if (n == 64) { if (n == 64) {
millis = 5; millis = POLL_INTERVAL_MS;
} else { } else {
millis = -1u; millis = -1u;
handles[n++] = __proc.onbirth; handles[n++] = __proc.onbirth;

View file

@ -167,7 +167,7 @@ static textwindows int __proc_wait(int pid, int *wstatus, int options,
} }
__proc_unlock(); __proc_unlock();
if (wi == 1) { if (wi == 1) {
// __sig_cancel() woke our semaphore // __sig_wake() woke our semaphore
continue; continue;
} else { } else {
// neither posix or win32 define i/o error conditions for // neither posix or win32 define i/o error conditions for

View file

@ -89,6 +89,7 @@ struct PosixThread {
locale_t pt_locale; locale_t pt_locale;
jmp_buf pt_exiter; jmp_buf pt_exiter;
pthread_attr_t pt_attr; pthread_attr_t pt_attr;
atomic_bool pt_intoff;
}; };
typedef void (*atfork_f)(void); typedef void (*atfork_f)(void);

View file

@ -43,7 +43,9 @@ errno_t pthread_kill(pthread_t thread, int sig) {
int err = 0; int err = 0;
struct PosixThread *pt; struct PosixThread *pt;
pt = (struct PosixThread *)thread; pt = (struct PosixThread *)thread;
if (!(1 <= sig && sig <= 64)) { if (!thread) {
err = EFAULT;
} else if (!(1 <= sig && sig <= 64)) {
err = EINVAL; err = EINVAL;
} else if (thread == __get_tls()->tib_pthread) { } else if (thread == __get_tls()->tib_pthread) {
err = raise(sig); // XNU will EDEADLK it otherwise err = raise(sig); // XNU will EDEADLK it otherwise
@ -62,9 +64,8 @@ errno_t pthread_kill(pthread_t thread, int sig) {
errno = e; errno = e;
} }
} }
if (err == ESRCH) { if (err == ESRCH)
err = 0; // we already reported this err = 0; // we already reported this
}
} }
STRACE("pthread_kill(%d, %G) → %s", _pthread_tid(pt), sig, STRACE("pthread_kill(%d, %G) → %s", _pthread_tid(pt), sig,
DescribeErrno(err)); DescribeErrno(err));

View file

@ -0,0 +1,105 @@
// Copyright 2024 Justine Alexandra Roberts Tunney
//
// Permission to use, copy, modify, and/or distribute this software for
// any purpose with or without fee is hereby granted, provided that the
// above copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
/**
* @fileoverview Tests two threads killing each other won't deadlock.
*
* Our Windows implementation of signals has surprisingly high
* throughput on this test. About 10x more signals get delivered than
* any other OS and in the same amount of time. The only exception was
* OpenBSD, which delivered a similar number of signals, but it took 10x
* longer for the process to execute.
*/
#define ITERATIONS 10000
int gotsigs[2];
pthread_t threads[2];
pthread_t thread_ids[2];
pthread_barrier_t barrier;
pthread_barrier_t barrier2;
void sig_handler(int signo) {
if (pthread_equal(pthread_self(), threads[0]))
++gotsigs[0];
if (pthread_equal(pthread_self(), threads[1]))
++gotsigs[1];
}
void *thread_func(void *arg) {
int idx = *(int *)arg;
int other_idx = 1 - idx;
thread_ids[idx] = pthread_self();
int s = pthread_barrier_wait(&barrier);
if (s != 0 && s != PTHREAD_BARRIER_SERIAL_THREAD)
exit(1);
pthread_t other_thread = thread_ids[other_idx];
for (int i = 0; i < ITERATIONS; ++i)
if (pthread_kill(other_thread, SIGUSR1))
exit(2);
s = pthread_barrier_wait(&barrier2);
if (s != 0 && s != PTHREAD_BARRIER_SERIAL_THREAD)
exit(1);
return 0;
}
int main() {
struct sigaction sa;
sa.sa_handler = sig_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGUSR1, &sa, 0) == -1)
exit(3);
if (pthread_barrier_init(&barrier, 0, 2))
exit(4);
if (pthread_barrier_init(&barrier2, 0, 2))
exit(4);
int idx0 = 0, idx1 = 1;
if (pthread_create(&threads[0], 0, thread_func, &idx0))
exit(5);
if (pthread_create(&threads[1], 0, thread_func, &idx1))
exit(6);
if (pthread_join(threads[0], 0))
exit(7);
if (pthread_join(threads[1], 0))
exit(8);
if (pthread_barrier_destroy(&barrier2))
exit(9);
if (pthread_barrier_destroy(&barrier))
exit(9);
if (!gotsigs[0])
exit(10);
if (!gotsigs[1])
exit(11);
return 0;
}

View file

@ -0,0 +1,170 @@
// Copyright 2024 Justine Alexandra Roberts Tunney
//
// Permission to use, copy, modify, and/or distribute this software for
// any purpose with or without fee is hereby granted, provided that the
// above copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
// PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "libc/thread/posixthread.internal.h"
#define ITERATIONS 10000
pthread_t sender_thread;
pthread_t receiver_thread;
struct timespec send_time;
double latencies[ITERATIONS];
void sender_signal_handler(int signo) {
// Empty handler to unblock sigsuspend()
}
void receiver_signal_handler(int signo) {
struct timespec receive_time;
if (clock_gettime(CLOCK_MONOTONIC, &receive_time) == -1)
exit(1);
long sec_diff = receive_time.tv_sec - send_time.tv_sec;
long nsec_diff = receive_time.tv_nsec - send_time.tv_nsec;
double latency_ns = sec_diff * 1e9 + nsec_diff;
static int iteration = 0;
if (iteration < ITERATIONS)
latencies[iteration++] = latency_ns;
// Send SIGUSR2 back to sender_thread
if (pthread_kill(sender_thread, SIGUSR2))
exit(2);
// Exit if we're done.
if (iteration >= ITERATIONS)
pthread_exit(0);
}
void *sender_func(void *arg) {
// Block SIGUSR2
sigset_t block_set;
sigemptyset(&block_set);
sigaddset(&block_set, SIGUSR2);
if (pthread_sigmask(SIG_BLOCK, &block_set, 0))
exit(3);
// Install signal handler for SIGUSR2
struct sigaction sa;
sa.sa_handler = sender_signal_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGUSR2, &sa, 0))
exit(4);
for (int i = 0; i < ITERATIONS; i++) {
if (clock_gettime(CLOCK_MONOTONIC, &send_time))
exit(5);
// Send SIGUSR1 to receiver_thread
if (pthread_kill(receiver_thread, SIGUSR1))
exit(6);
// Unblock SIGUSR2 and wait for it
sigset_t wait_set;
sigemptyset(&wait_set);
if (sigsuspend(&wait_set) && errno != EINTR)
exit(7);
}
return 0;
}
void *receiver_func(void *arg) {
// Install signal handler for SIGUSR1
struct sigaction sa;
sa.sa_handler = receiver_signal_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGUSR1, &sa, 0))
exit(8);
// Block all signals except SIGUSR1
sigset_t block_set;
sigfillset(&block_set);
sigdelset(&block_set, SIGUSR1);
if (pthread_sigmask(SIG_SETMASK, &block_set, 0))
exit(9);
// Wait indefinitely for signals
while (1)
pause();
return 0;
}
int compare(const void *a, const void *b) {
const double *x = a, *y = b;
if (*x < *y)
return -1;
else if (*x > *y)
return 1;
else
return 0;
}
int main() {
// Block SIGUSR1 and SIGUSR2 in main thread
sigset_t block_set;
sigemptyset(&block_set);
sigaddset(&block_set, SIGUSR1);
sigaddset(&block_set, SIGUSR2);
if (pthread_sigmask(SIG_BLOCK, &block_set, 0))
exit(10);
// Create receiver thread first
if (pthread_create(&receiver_thread, 0, receiver_func, 0))
exit(11);
// Create sender thread
if (pthread_create(&sender_thread, 0, sender_func, 0))
exit(12);
// Wait for threads to finish
if (pthread_join(sender_thread, 0))
exit(13);
if (pthread_join(receiver_thread, 0))
exit(14);
// Compute mean latency
double total_latency = 0;
for (int i = 0; i < ITERATIONS; i++)
total_latency += latencies[i];
double mean_latency = total_latency / ITERATIONS;
// Sort latencies to compute percentiles
qsort(latencies, ITERATIONS, sizeof(double), compare);
double p50 = latencies[(int)(0.50 * ITERATIONS)];
double p90 = latencies[(int)(0.90 * ITERATIONS)];
double p95 = latencies[(int)(0.95 * ITERATIONS)];
double p99 = latencies[(int)(0.99 * ITERATIONS)];
printf("Mean latency: %.2f ns\n", mean_latency);
printf("50th percentile latency: %.2f ns\n", p50);
printf("90th percentile latency: %.2f ns\n", p90);
printf("95th percentile latency: %.2f ns\n", p95);
printf("99th percentile latency: %.2f ns\n", p99);
}

View file

@ -182,7 +182,6 @@ static void free_waiters_populate (void) {
int n; int n;
if (IsNetbsd ()) { if (IsNetbsd ()) {
// netbsd needs a real file descriptor per semaphore // netbsd needs a real file descriptor per semaphore
// tim cook wants us to use his lol central dispatch
n = 1; n = 1;
} else { } else {
n = __pagesize / sizeof(waiter); n = __pagesize / sizeof(waiter);

View file

@ -185,7 +185,7 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare,
} }
ok = WaitOnAddress (w, &expect, sizeof(int), nsync_time_64to32u (timespec_tomillis (wait))); ok = WaitOnAddress (w, &expect, sizeof(int), nsync_time_64to32u (timespec_tomillis (wait)));
if (pt) { if (pt) {
/* __sig_cancel wakes our futex without changing `w` after enqueing signals */ /* __sig_wake wakes our futex without changing `w` after enqueing signals */
atomic_store_explicit (&pt->pt_blocker, 0, memory_order_release); atomic_store_explicit (&pt->pt_blocker, 0, memory_order_release);
if (ok && atomic_load_explicit (w, memory_order_acquire) == expect && (sig = __sig_get (waitmask))) { if (ok && atomic_load_explicit (w, memory_order_acquire) == expect && (sig = __sig_get (waitmask))) {
__sig_relay (sig, SI_KERNEL, waitmask); __sig_relay (sig, SI_KERNEL, waitmask);