diff --git a/libc/calls/clock_nanosleep-nt.c b/libc/calls/clock_nanosleep-nt.c index 1546447a9..a74f056ef 100644 --- a/libc/calls/clock_nanosleep-nt.c +++ b/libc/calls/clock_nanosleep-nt.c @@ -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; } } diff --git a/libc/calls/park.c b/libc/calls/park.c index 71f203128..dcd4458d1 100644 --- a/libc/calls/park.c +++ b/libc/calls/park.c @@ -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; } diff --git a/libc/calls/pause-nt.c b/libc/calls/pause-nt.c index 0a43e5089..3ba95f8c6 100644 --- a/libc/calls/pause-nt.c +++ b/libc/calls/pause-nt.c @@ -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; } diff --git a/libc/calls/sig.c b/libc/calls/sig.c index 48137ec34..196de3397 100644 --- a/libc/calls/sig.c +++ b/libc/calls/sig.c @@ -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; } diff --git a/libc/calls/sigcheck.c b/libc/calls/sigcheck.c index 74cbcafd1..e8cad756d 100644 --- a/libc/calls/sigcheck.c +++ b/libc/calls/sigcheck.c @@ -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; } diff --git a/libc/calls/sigsuspend.c b/libc/calls/sigsuspend.c index 134eda44c..fa4041c5f 100644 --- a/libc/calls/sigsuspend.c +++ b/libc/calls/sigsuspend.c @@ -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); } diff --git a/libc/intrin/itoa16.c b/libc/intrin/itoa16.c index 2d8055887..003aba59c 100644 --- a/libc/intrin/itoa16.c +++ b/libc/intrin/itoa16.c @@ -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 { diff --git a/libc/intrin/sig.c b/libc/intrin/sig.c index 8679b811d..4fdd97914 100644 --- a/libc/intrin/sig.c +++ b/libc/intrin/sig.c @@ -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); diff --git a/libc/intrin/sigproc.c b/libc/intrin/sigproc.c index 644d77263..1c8adf72e 100644 --- a/libc/intrin/sigproc.c +++ b/libc/intrin/sigproc.c @@ -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'; diff --git a/libc/intrin/sigprocmask-nt.c b/libc/intrin/sigprocmask-nt.c index 7281938c8..72ee8d79b 100644 --- a/libc/intrin/sigprocmask-nt.c +++ b/libc/intrin/sigprocmask-nt.c @@ -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; } diff --git a/libc/log/oncrash_amd64.c b/libc/log/oncrash_amd64.c index 8d51c71f8..f2726bb19 100644 --- a/libc/log/oncrash_amd64.c +++ b/libc/log/oncrash_amd64.c @@ -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]); diff --git a/libc/proc/proc.c b/libc/proc/proc.c index cfb1e5f30..bf3d08a75 100644 --- a/libc/proc/proc.c +++ b/libc/proc/proc.c @@ -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; diff --git a/libc/proc/wait4-nt.c b/libc/proc/wait4-nt.c index 110994984..9ea695ecf 100644 --- a/libc/proc/wait4-nt.c +++ b/libc/proc/wait4-nt.c @@ -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 diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index 40c84330d..829217fa2 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -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); diff --git a/libc/thread/pthread_kill.c b/libc/thread/pthread_kill.c index 472da205e..f57a99c55 100644 --- a/libc/thread/pthread_kill.c +++ b/libc/thread/pthread_kill.c @@ -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)); diff --git a/test/posix/signal_fight_test.c b/test/posix/signal_fight_test.c new file mode 100644 index 000000000..910a7e04a --- /dev/null +++ b/test/posix/signal_fight_test.c @@ -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 +#include +#include + +/** + * @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; +} diff --git a/test/posix/signal_latency_test.c b/test/posix/signal_latency_test.c new file mode 100644 index 000000000..02929aa8c --- /dev/null +++ b/test/posix/signal_latency_test.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/third_party/nsync/common.c b/third_party/nsync/common.c index 6daf2b8c1..a7fd4a068 100644 --- a/third_party/nsync/common.c +++ b/third_party/nsync/common.c @@ -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); diff --git a/third_party/nsync/futex.c b/third_party/nsync/futex.c index dc550276c..b7662a544 100644 --- a/third_party/nsync/futex.c +++ b/third_party/nsync/futex.c @@ -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);