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;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
|
|
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;
|
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);
|
||||||
|
|
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)));
|
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);
|
||||||
|
|
Loading…
Reference in a new issue