mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 06:53:33 +00:00
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:
parent
0e59afb403
commit
dd8c4dbd7d
19 changed files with 407 additions and 75 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -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];
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -321,17 +325,16 @@ 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;
|
||||
|
@ -339,10 +342,9 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
|
|||
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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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__
|
||||
|
@ -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';
|
||||
|
|
|
@ -47,6 +47,9 @@ textwindows int __sig_mask(int how, const sigset_t *neu, sigset_t *old) {
|
|||
} else { // SIG_SETMASK
|
||||
oldmask = atomic_exchange_explicit(mask, *neu, memory_order_acq_rel);
|
||||
}
|
||||
if (_weaken(__sig_check)) {
|
||||
_weaken(__sig_check)();
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
if (_weaken(__sig_check)) {
|
||||
_weaken(__sig_check)();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -246,6 +246,7 @@ static relegated void ShowCrashReport(int err, int sig, siginfo_t *si,
|
|||
if (g_fds.n)
|
||||
kprintf("\n");
|
||||
__printfds(g_fds.p, g_fds.n);
|
||||
kprintf("\n");
|
||||
if (__argv)
|
||||
for (i = 0; i < __argc; ++i)
|
||||
kprintf("%s ", __argv[i]);
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "libc/calls/struct/rusage.h"
|
||||
#include "libc/calls/struct/siginfo.h"
|
||||
#include "libc/calls/struct/sigset.internal.h"
|
||||
#include "libc/calls/syscall_support-nt.internal.h"
|
||||
#include "libc/cosmo.h"
|
||||
#include "libc/errno.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
|
||||
if (n == 64) {
|
||||
millis = 5;
|
||||
millis = POLL_INTERVAL_MS;
|
||||
} else {
|
||||
millis = -1u;
|
||||
handles[n++] = __proc.onbirth;
|
||||
|
|
|
@ -167,7 +167,7 @@ static textwindows int __proc_wait(int pid, int *wstatus, int options,
|
|||
}
|
||||
__proc_unlock();
|
||||
if (wi == 1) {
|
||||
// __sig_cancel() woke our semaphore
|
||||
// __sig_wake() woke our semaphore
|
||||
continue;
|
||||
} else {
|
||||
// neither posix or win32 define i/o error conditions for
|
||||
|
|
|
@ -89,6 +89,7 @@ struct PosixThread {
|
|||
locale_t pt_locale;
|
||||
jmp_buf pt_exiter;
|
||||
pthread_attr_t pt_attr;
|
||||
atomic_bool pt_intoff;
|
||||
};
|
||||
|
||||
typedef void (*atfork_f)(void);
|
||||
|
|
|
@ -43,7 +43,9 @@ errno_t pthread_kill(pthread_t thread, int sig) {
|
|||
int err = 0;
|
||||
struct PosixThread *pt;
|
||||
pt = (struct PosixThread *)thread;
|
||||
if (!(1 <= sig && sig <= 64)) {
|
||||
if (!thread) {
|
||||
err = EFAULT;
|
||||
} else if (!(1 <= sig && sig <= 64)) {
|
||||
err = EINVAL;
|
||||
} else if (thread == __get_tls()->tib_pthread) {
|
||||
err = raise(sig); // XNU will EDEADLK it otherwise
|
||||
|
@ -62,9 +64,8 @@ errno_t pthread_kill(pthread_t thread, int sig) {
|
|||
errno = e;
|
||||
}
|
||||
}
|
||||
if (err == ESRCH) {
|
||||
if (err == ESRCH)
|
||||
err = 0; // we already reported this
|
||||
}
|
||||
}
|
||||
STRACE("pthread_kill(%d, %G) → %s", _pthread_tid(pt), sig,
|
||||
DescribeErrno(err));
|
||||
|
|
105
test/posix/signal_fight_test.c
Normal file
105
test/posix/signal_fight_test.c
Normal 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;
|
||||
}
|
170
test/posix/signal_latency_test.c
Normal file
170
test/posix/signal_latency_test.c
Normal 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);
|
||||
}
|
1
third_party/nsync/common.c
vendored
1
third_party/nsync/common.c
vendored
|
@ -182,7 +182,6 @@ static void free_waiters_populate (void) {
|
|||
int n;
|
||||
if (IsNetbsd ()) {
|
||||
// netbsd needs a real file descriptor per semaphore
|
||||
// tim cook wants us to use his lol central dispatch
|
||||
n = 1;
|
||||
} else {
|
||||
n = __pagesize / sizeof(waiter);
|
||||
|
|
2
third_party/nsync/futex.c
vendored
2
third_party/nsync/futex.c
vendored
|
@ -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)));
|
||||
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);
|
||||
if (ok && atomic_load_explicit (w, memory_order_acquire) == expect && (sig = __sig_get (waitmask))) {
|
||||
__sig_relay (sig, SI_KERNEL, waitmask);
|
||||
|
|
Loading…
Reference in a new issue