Introduce sigtimedwait() on Windows

This commit is contained in:
Justine Tunney 2024-09-14 20:32:46 -07:00
parent 37e2660c7f
commit c260144843
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
17 changed files with 280 additions and 130 deletions

View file

@ -44,8 +44,6 @@
#include "libc/thread/posixthread.internal.h"
#ifdef __x86_64__
#define POLL_INTERVAL_MS 10
// <sync libc/sysv/consts.sh>
#define POLLERR_ 0x0001 // implied in events
#define POLLHUP_ 0x0002 // implied in events

View file

@ -58,7 +58,6 @@
* @return fds[𝑖].revents is always zero initializaed and then will
* be populated with POLL{IN,OUT,PRI,HUP,ERR,NVAL} if something
* was determined about the file descriptor
* @raise EINVAL if we exceeded the 64 socket limit on Windows
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINVAL if `nfds` exceeded `RLIMIT_NOFILE`
* @raise ENOMEM on failure to allocate memory

View file

@ -35,80 +35,14 @@
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
/**
* Checks status on multiple file descriptors at once.
*
* This function is the same as saying:
*
* sigset_t old;
* sigprocmask(SIG_SETMASK, sigmask, &old);
* poll(fds, nfds, timeout);
* sigprocmask(SIG_SETMASK, old, 0);
*
* Except it happens atomically when the kernel supports doing that. On
* kernels such as XNU and NetBSD which don't, this wrapper will fall
* back to using the example above. If you need ironclad assurances of
* signal mask atomicity, then consider using pselect() which Cosmo Libc
* guarantees to be atomic on all supported platforms.
*
* Servers that need to handle an unbounded number of client connections
* should just create a separate thread for each client. poll(), ppoll()
* and select() aren't scalable i/o solutions on any platform.
*
* On Windows it's only possible to poll 64 file descriptors at a time;
* it's a limitation imposed by WSAPoll(). Cosmopolitan Libc's ppoll()
* polyfill can go higher in some cases; for example, It's possible to
* poll 64 sockets and 64 pipes/terminals at the same time. Furthermore,
* elements whose fd field is set to a negative number are ignored and
* will not count against this limit.
*
* One of the use cases for poll() is to quickly check if a number of
* file descriptors are valid. The canonical way to do this is to set
* events to 0 which prevents blocking and causes only the invalid,
* hangup, and error statuses to be checked.
*
* On XNU, the POLLHUP and POLLERR statuses aren't checked unless either
* POLLIN, POLLOUT, or POLLPRI are specified in the events field. Cosmo
* will however polyfill the checking of POLLNVAL on XNU with the events
* doesn't specify any of the above i/o events.
*
* When XNU and BSD OSes report POLLHUP, they will always set POLLIN too
* when POLLIN is requested, even in cases when there isn't unread data.
*
* @param fds[𝑖].fd should be a socket, input pipe, or conosle input
* and if it's a negative number then the entry is ignored, plus
* revents will be set to zero
* @param fds[𝑖].events flags can have POLLIN, POLLOUT, POLLPRI,
* POLLRDNORM, POLLWRNORM, POLLRDBAND, POLLWRBAND as well as
* POLLERR, POLLHUP, and POLLNVAL although the latter are
* always implied (assuming fd0) so they're ignored here
* @param timeout_ms if 0 means don't wait and negative waits forever
* @return number of `fds` whose revents field has been set to a nonzero
* number, 0 if the timeout elapsed without events, or -1 w/ errno
* @return fds[𝑖].revents is always zero initializaed and then will
* be populated with POLL{IN,OUT,PRI,HUP,ERR,NVAL} if something
* was determined about the file descriptor
* @param timeout if null will block indefinitely
* @param sigmask may be null in which case no mask change happens
* @raise EINVAL if we exceeded the 64 socket limit on Windows
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINVAL if `nfds` exceeded `RLIMIT_NOFILE`
* @raise ENOMEM on failure to allocate memory
* @raise EINVAL if `*timeout` is invalid
* @raise EINTR if signal was delivered
* @cancelationpoint
* @asyncsignalsafe
* @norestart
*/
int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
const sigset_t *sigmask) {
static int ppoll_impl(struct pollfd *fds, size_t nfds,
const struct timespec *timeout, const sigset_t *sigmask) {
int e, fdcount;
sigset_t oldmask;
struct timespec ts, *tsp;
BEGIN_CANCELATION_POINT;
// validate timeout
if (timeout && timeout->tv_nsec >= 1000000000)
if (timeout && timeout->tv_nsec >= 1000000000ull)
return einval();
// The OpenBSD poll() man pages claims it'll ignore POLLERR, POLLHUP,
@ -192,6 +126,78 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
}
}
return fdcount;
}
/**
* Checks status on multiple file descriptors at once.
*
* This function is the same as saying:
*
* sigset_t old;
* sigprocmask(SIG_SETMASK, sigmask, &old);
* poll(fds, nfds, timeout);
* sigprocmask(SIG_SETMASK, old, 0);
*
* Except it happens atomically when the kernel supports doing that. On
* kernels such as XNU and NetBSD which don't, this wrapper will fall
* back to using the example above. If you need ironclad assurances of
* signal mask atomicity, then consider using pselect() which Cosmo Libc
* guarantees to be atomic on all supported platforms.
*
* Servers that need to handle an unbounded number of client connections
* should just create a separate thread for each client. poll(), ppoll()
* and select() aren't scalable i/o solutions on any platform.
*
* On Windows it's only possible to poll 64 file descriptors at a time;
* it's a limitation imposed by WSAPoll(). Cosmopolitan Libc's ppoll()
* polyfill can go higher in some cases; for example, It's possible to
* poll 64 sockets and 64 pipes/terminals at the same time. Furthermore,
* elements whose fd field is set to a negative number are ignored and
* will not count against this limit.
*
* One of the use cases for poll() is to quickly check if a number of
* file descriptors are valid. The canonical way to do this is to set
* events to 0 which prevents blocking and causes only the invalid,
* hangup, and error statuses to be checked.
*
* On XNU, the POLLHUP and POLLERR statuses aren't checked unless either
* POLLIN, POLLOUT, or POLLPRI are specified in the events field. Cosmo
* will however polyfill the checking of POLLNVAL on XNU with the events
* doesn't specify any of the above i/o events.
*
* When XNU and BSD OSes report POLLHUP, they will always set POLLIN too
* when POLLIN is requested, even in cases when there isn't unread data.
*
* @param fds[𝑖].fd should be a socket, input pipe, or conosle input
* and if it's a negative number then the entry is ignored, plus
* revents will be set to zero
* @param fds[𝑖].events flags can have POLLIN, POLLOUT, POLLPRI,
* POLLRDNORM, POLLWRNORM, POLLRDBAND, POLLWRBAND as well as
* POLLERR, POLLHUP, and POLLNVAL although the latter are
* always implied (assuming fd0) so they're ignored here
* @param timeout_ms if 0 means don't wait and negative waits forever
* @return number of `fds` whose revents field has been set to a nonzero
* number, 0 if the timeout elapsed without events, or -1 w/ errno
* @return fds[𝑖].revents is always zero initializaed and then will
* be populated with POLL{IN,OUT,PRI,HUP,ERR,NVAL} if something
* was determined about the file descriptor
* @param timeout if null will block indefinitely
* @param sigmask may be null in which case no mask change happens
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINVAL if `nfds` exceeded `RLIMIT_NOFILE`
* @raise ENOMEM on failure to allocate memory
* @raise EINVAL if `*timeout` is invalid
* @raise EINTR if signal was delivered
* @cancelationpoint
* @asyncsignalsafe
* @norestart
*/
int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
const sigset_t *sigmask) {
int fdcount;
BEGIN_CANCELATION_POINT;
fdcount = ppoll_impl(fds, nfds, timeout, sigmask);
END_CANCELATION_POINT;
STRACE("ppoll(%s, %'zu, %s, %s) → %d% lm",
DescribePollFds(fdcount, fds, nfds), nfds,

View file

@ -302,6 +302,15 @@ 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))) {
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
memory_order_relaxed);
__sig_cancel(pt, sig, flags);
return 0;
}
// if there's no handler then killing a thread kills the process
if (rva == (intptr_t)SIG_DFL) {
STRACE("terminating on %G due to no handler", sig);

View file

@ -0,0 +1,113 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
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 "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/macros.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
textwindows static int sys_sigtimedwait_nt_check(sigset_t syncsigs,
siginfo_t *opt_info,
sigset_t waitmask) {
int sig;
if (_check_cancel() == -1)
return -1;
if ((sig = __sig_get(waitmask))) {
if ((1ull << (sig - 1)) & syncsigs) {
if (opt_info) {
memset(opt_info, 0, sizeof(*opt_info));
opt_info->si_signo = sig;
opt_info->si_code = SI_TKILL;
opt_info->si_uid = sys_getuid_nt();
}
return sig;
}
int handler_was_called = __sig_relay(sig, SI_TKILL, waitmask);
if (_check_cancel() == -1)
return -1;
if (handler_was_called)
return eintr();
}
return 0;
}
textwindows static int sys_sigtimedwait_nt_impl(sigset_t syncsigs,
siginfo_t *opt_info,
struct timespec deadline,
sigset_t waitmask,
intptr_t semaphore) {
for (;;) {
int sig;
if ((sig = sys_sigtimedwait_nt_check(syncsigs, opt_info, waitmask)))
return sig;
struct timespec now = sys_clock_gettime_monotonic_nt();
if (timespec_cmp(now, deadline) >= 0)
return eagain();
struct timespec remain = timespec_sub(deadline, now);
int64_t millis = timespec_tomillis(remain);
uint32_t waitms = MIN(millis, 0xffffffffu);
uint32_t wi = WaitForSingleObject(semaphore, waitms);
if (wi == -1u)
return __winerr();
if (wi)
return eagain();
}
}
textwindows int sys_sigtimedwait_nt(const sigset_t *set, siginfo_t *opt_info,
const struct timespec *opt_timeout) {
int rc;
intptr_t sem;
struct PosixThread *pt;
struct timespec deadline;
sigset_t syncsigs, waitmask;
BLOCK_SIGNALS;
if (opt_timeout) {
deadline = timespec_add(sys_clock_gettime_monotonic_nt(), *opt_timeout);
} else {
deadline = timespec_max;
}
if ((sem = CreateSemaphore(0, 0, 1, 0))) {
syncsigs = *set & ~(1ull << (SIGTHR - 1)); // internal to pthreads
waitmask = ~syncsigs & _SigMask;
pt = _pthread_self();
pt->pt_blkmask = waitmask;
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM,
memory_order_release);
rc = sys_sigtimedwait_nt_impl(syncsigs, opt_info, deadline, waitmask, sem);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
CloseHandle(sem);
} else {
rc = __winerr();
}
ALLOW_SIGNALS;
return rc;
}

View file

@ -27,48 +27,62 @@
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
int sys_sigtimedwait_nt(const sigset_t *, siginfo_t *, const struct timespec *);
/**
* Waits for signal synchronously, w/ timeout.
*
* This function does not change the thread signal mask. Signals that
* aren't masked, which aren't in `set`, will be handled normally, in
* which case this function will raise `EINTR`.
*
* This function silently ignores attempts to synchronously wait for
* SIGTHR which is used internally by the POSIX threads implementation.
*
* @param set is signals for which we'll be waiting
* @param info if not null shall receive info about signal
* @param timeout is relative deadline and null means wait forever
* @param opt_info if not null shall receive info about signal
* @param opt_timeout is relative deadline and null means wait forever
* @return signal number on success, or -1 w/ errno
* @raise EINTR if an asynchronous signal was delivered instead
* @raise ECANCELED if thread was cancelled in masked mode
* @raise EINVAL if nanoseconds parameter was out of range
* @raise EAGAIN if deadline expired
* @raise ENOSYS on Windows, XNU, OpenBSD, Metal
* @raise EAGAIN if timeout elapsed
* @raise ENOSYS on XNU, OpenBSD, and Metal
* @raise EFAULT if invalid memory was supplied
* @cancelationpoint
* @norestart
*/
int sigtimedwait(const sigset_t *set, siginfo_t *info,
const struct timespec *timeout) {
int sigtimedwait(const sigset_t *set, siginfo_t *opt_info,
const struct timespec *opt_timeout) {
int rc;
char strsig[21];
struct timespec ts;
union siginfo_meta si = {0};
BEGIN_CANCELATION_POINT;
if (IsLinux() || IsFreebsd() || IsNetbsd()) {
if (timeout) {
// validate timeout
if (opt_timeout && opt_timeout->tv_nsec >= 1000000000ull) {
rc = einval();
} else if (IsLinux() || IsFreebsd() || IsNetbsd()) {
if (opt_timeout) {
// 1. Linux needs its size parameter
// 2. NetBSD modifies timeout argument
ts = *timeout;
ts = *opt_timeout;
rc = sys_sigtimedwait(set, &si, &ts, 8);
} else {
rc = sys_sigtimedwait(set, &si, 0, 8);
}
if (rc != -1 && info) {
__siginfo2cosmo(info, &si);
}
if (rc != -1 && opt_info)
__siginfo2cosmo(opt_info, &si);
} else if (IsWindows()) {
rc = sys_sigtimedwait_nt(set, opt_info, opt_timeout);
} else {
rc = enosys();
}
END_CANCELATION_POINT;
STRACE("sigtimedwait(%s, [%s], %s) → %s% m", DescribeSigset(0, set),
DescribeSiginfo(rc, info), DescribeTimespec(0, timeout),
DescribeSiginfo(rc, opt_info), DescribeTimespec(0, opt_timeout),
strsignal_r(rc, strsig));
return rc;
}

View file

@ -18,10 +18,26 @@
*/
#include "libc/calls/sigtimedwait.h"
int sigwait(const sigset_t *mask, int *sig) {
siginfo_t si;
if (sigtimedwait(mask, &si, 0) < 0)
/**
* Waits for signal synchronously.
*
* See sigtimedwait() for further details.
*
* @param set is signals for which we'll be waiting
* @param out_sig shall receive signal number
* @return 0 on success, or -1 w/ errno
* @raise EINTR if an asynchronous signal was delivered instead
* @raise ECANCELED if thread was cancelled in masked mode
* @raise ENOSYS on OpenBSD, XNU, and Metal
* @see sigtimedwait()
* @cancelationpoint
* @norestart
*/
int sigwait(const sigset_t *mask, int *out_sig) {
int sig;
if ((sig = sigtimedwait(mask, 0, 0)) == -1)
return -1;
*sig = si.si_signo;
if (out_sig)
*out_sig = sig;
return 0;
}

View file

@ -21,14 +21,17 @@
/**
* Waits for signal synchronously.
*
* See sigtimedwait() for further details.
*
* @param set is signals for which we'll be waiting
* @param info if not null shall receive info about signal
* @return signal number on success, or -1 w/ errno
* @raise EINTR if an asynchronous signal was delivered instead
* @raise ECANCELED if thread was cancelled in masked mode
* @raise ENOSYS on OpenBSD, XNU, and Windows
* @raise ENOSYS on OpenBSD, XNU, and Metal
* @see sigtimedwait()
* @cancelationpoint
* @norestart
*/
int sigwaitinfo(const sigset_t *mask, siginfo_t *si) {
return sigtimedwait(mask, si, 0);

View file

@ -4,6 +4,8 @@
#include "libc/nt/struct/overlapped.h"
COSMOPOLITAN_C_START_
#define POLL_INTERVAL_MS 10
bool isdirectory_nt(const char *);
bool isregularfile_nt(const char *);
bool issymlink_nt(const char *);

View file

@ -18,6 +18,7 @@
*/
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/nt/errors.h"
#include "libc/nt/struct/pollfd.h"
@ -33,8 +34,6 @@
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
#define POLL_INTERVAL_MS 10
__msabi extern typeof(__sys_ioctlsocket_nt) *const __imp_ioctlsocket;
textwindows static int sys_accept_nt_impl(struct Fd *f,

View file

@ -19,6 +19,7 @@
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/macros.h"
#include "libc/nt/errors.h"
@ -42,8 +43,6 @@
#define CONNECTING 1
#define CONNECTED 2
#define POLL_INTERVAL_MS 10
__msabi extern typeof(__sys_getsockopt_nt) *const __imp_getsockopt;
__msabi extern typeof(__sys_ioctlsocket_nt) *const __imp_ioctlsocket;
__msabi extern typeof(__sys_select_nt) *const __imp_select;

View file

@ -33,7 +33,7 @@ void CheckForFileLeaks(void) {
char *pe = msg + 256;
bool gotsome = false;
if (IsQemuUser())
usleep(1); // weird qemu mt flake
usleep(10000); // weird qemu mt flake
for (int fd = 3; fd < MIN_CLANDESTINE_FD; ++fd) {
if (fcntl(fd, F_GETFL) != -1) {
if (!gotsome) {

View file

@ -100,8 +100,6 @@ TEST(commandv, test_DirPaths_wontConsiderDirectoriesExecutable2) {
}
TEST(commandv, test_nonExecutableFile_willEacces) {
if (IsWindows())
return; // TODO: fixme
setenv("PATH", "foo", true);
EXPECT_SYS(0, 0, mkdir("foo", 0755));
EXPECT_SYS(0, 0, touch("foo/bar", 0400));

View file

@ -59,11 +59,9 @@ void *TortureWorker(void *arg) {
ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &ss, 0));
ready = true;
while (!done) {
if (!IsWindows())
pthread_kill(parent, SIGUSR1);
pthread_kill(parent, SIGUSR1);
usleep(1);
if (!IsWindows())
pthread_kill(parent, SIGUSR2);
pthread_kill(parent, SIGUSR2);
usleep(1);
}
return 0;

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/sigtimedwait.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/calls/struct/siginfo.internal.h"
@ -28,22 +29,17 @@
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/thread.h"
void SetUp(void) {
if (IsXnu())
exit(0);
if (IsMetal())
exit(0);
if (IsWindows())
exit(0);
if (IsOpenbsd())
exit(0);
}
TEST(sigtimedwait, nullSet_efault) {
ASSERT_SYS(EFAULT, -1, sigtimedwait(0, 0, 0));
}
TEST(sigtimedwait, emptySet_timesOut) {
sigset_t ss = {0};
struct timespec ts = {0, 0};
@ -56,24 +52,28 @@ TEST(sigtimedwait, badTimestamp_einval) {
ASSERT_SYS(EINVAL, -1, sigtimedwait(&ss, 0, &ts));
}
TEST(sigtimedwait, test) {
int pid, ws;
atomic_bool g_ready;
void *worker(void *arg) {
sigset_t ss;
siginfo_t info;
sigset_t ss, oldss;
struct timespec ts = {1, 0};
sigemptyset(&ss);
sigaddset(&ss, SIGUSR1);
ASSERT_SYS(0, 0, sigprocmask(SIG_BLOCK, &ss, &oldss));
ASSERT_NE(-1, (pid = fork()));
if (!pid) {
ASSERT_SYS(0, SIGUSR1, sigtimedwait(&ss, &info, &ts));
ASSERT_EQ(SIGUSR1, info.si_signo);
ASSERT_EQ(SI_USER, info.si_code);
ASSERT_EQ(getuid(), info.si_uid);
_Exit(0);
}
ASSERT_SYS(0, 0, kill(pid, SIGUSR1));
ASSERT_SYS(0, pid, wait(&ws));
ASSERT_EQ(0, ws);
ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &oldss, 0));
ASSERT_EQ(0, sigemptyset(&ss));
ASSERT_EQ(0, sigaddset(&ss, SIGUSR1));
ASSERT_SYS(0, 0, sigprocmask(SIG_BLOCK, &ss, 0));
g_ready = true;
ASSERT_SYS(0, SIGUSR1, sigtimedwait(&ss, &info, 0));
ASSERT_EQ(SIGUSR1, info.si_signo);
ASSERT_EQ(SI_TKILL, info.si_code);
ASSERT_EQ(getuid(), info.si_uid);
return 0;
}
TEST(sigtimedwait, test) {
pthread_t th;
ASSERT_EQ(0, pthread_create(&th, 0, worker, 0));
for (;;)
if (g_ready)
break;
ASSERT_EQ(0, pthread_kill(th, SIGUSR1));
ASSERT_EQ(0, pthread_join(th, 0));
}

View file

@ -103,8 +103,6 @@ TEST(pthread_cancel, synchronous) {
TEST(pthread_cancel, synchronous_deferred) {
void *rc;
pthread_t th;
if (!IsWindows())
return;
ASSERT_SYS(0, 0, pipe(pfds));
ASSERT_EQ(0, pthread_create(&th, 0, Worker, 0));
while (!ready)

View file

@ -193,8 +193,6 @@ void *SocketAcceptWorker(void *arg) {
}
TEST(pthread_kill, canInterruptSocketAcceptOperation) {
if (IsWindows())
return; // TODO(jart): BAH
pthread_t t;
struct sigaction oldsa;
struct sigaction sa = {.sa_handler = OnSig};