Rewrite Windows accept()

This change should fix the Windows issues Qt Creator has been having, by
ensuring accept() and accept4() work in O_NONBLOCK mode. I switched away
from AcceptEx() which is buggy, back to using WSAAccept(). This requires
making a tradeoff where we have to accept a busy loop. However it is low
latency in nature, just like our new and improved Windows poll() code. I
was furthermore able to eliminate a bunch of Windows-related test todos.
This commit is contained in:
Justine Tunney 2024-09-12 01:18:14 -07:00
parent 6f868fe1de
commit acd6c32184
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
20 changed files with 622 additions and 209 deletions

View file

@ -21,7 +21,7 @@
#include "libc/intrin/weaken.h"
#include "libc/thread/posixthread.internal.h"
int _check_cancel(void) {
textwindows int _check_cancel(void) {
if (_weaken(_pthread_cancel_ack) && //
_pthread_self() && !(_pthread_self()->pt_flags & PT_NOCANCEL) &&
atomic_load_explicit(&_pthread_self()->pt_canceled,

View file

@ -25,6 +25,7 @@ int __ensurefds(int);
uint32_t sys_getuid_nt(void);
int __ensurefds_unlocked(int);
void __printfds(struct Fd *, size_t);
int __sigcheck(sigset_t, bool);
int CountConsoleInputBytes(void);
int FlushConsoleInputBytes(void);
int64_t GetConsoleInputHandle(void);

View file

@ -17,25 +17,19 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/weaken.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__
// returns 0 on timeout or spurious wakeup
// raises EINTR if a signal delivery interrupted wait operation
// raises ECANCELED if this POSIX thread was canceled in masked mode
static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask,
textwindows static int _park_thread(uint32_t msdelay, sigset_t waitmask,
bool restartable) {
int sig, handler_was_called;
if (_check_cancel() == -1)
if (__sigcheck(waitmask, restartable) == -1)
return -1;
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask)))
goto HandleSignal;
int expect = 0;
atomic_int futex = 0;
struct PosixThread *pt = _pthread_self();
@ -43,17 +37,8 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t 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, 0, memory_order_release);
if (ok && _weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
HandleSignal:
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 (ok && __sigcheck(waitmask, restartable) == -1)
return -1;
return 0;
}

View file

@ -16,23 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/fds.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
#include "libc/macros.h"
#include "libc/mem/mem.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/enum/wait.h"
@ -42,22 +34,13 @@
#include "libc/nt/runtime.h"
#include "libc/nt/struct/pollfd.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/time.h"
#include "libc/nt/winsock.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/internal.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sock/struct/pollfd.internal.h"
#include "libc/stdio/sysparam.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/poll.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
#define POLL_INTERVAL_MS 10
@ -75,24 +58,22 @@
#define POLLPRI_ 0x0400 // MSDN unsupported
// </sync libc/sysv/consts.sh>
textwindows static dontinline struct timespec sys_poll_nt_now(void) {
textwindows dontinline static struct timespec sys_poll_nt_now(void) {
uint64_t hectons;
QueryUnbiasedInterruptTimePrecise(&hectons);
return timespec_fromnanos(hectons * 100);
}
textwindows static int sys_poll_nt_sigcheck(sigset_t sigmask) {
int sig, handler_was_called;
if (_check_cancel() == -1)
return -1;
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(sigmask))) {
handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, sigmask);
if (_check_cancel() == -1)
return -1;
if (handler_was_called)
return eintr();
textwindows static uint32_t sys_poll_nt_waitms(struct timespec deadline) {
struct timespec now = sys_poll_nt_now();
if (timespec_cmp(now, deadline) < 0) {
struct timespec remain = timespec_sub(deadline, now);
int64_t millis = timespec_tomillis(remain);
uint32_t waitfor = MIN(millis, 0xffffffffu);
return MIN(waitfor, POLL_INTERVAL_MS);
} else {
return 0; // we timed out
}
return 0;
}
// Polls on the New Technology.
@ -100,22 +81,17 @@ textwindows static int sys_poll_nt_sigcheck(sigset_t sigmask) {
// This function is used to implement poll() and select(). You may poll
// on sockets, files and the console at the same time. We also poll for
// both signals and posix thread cancelation, while the poll is polling
textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
uint32_t *ms, sigset_t sigmask) {
bool ok;
uint64_t millis;
textwindows static int sys_poll_nt_actual(struct pollfd *fds, uint64_t nfds,
struct timespec deadline,
sigset_t waitmask) {
int fileindices[64];
int sockindices[64];
int64_t filehands[64];
struct PosixThread *pt;
int i, rc, ev, kind, gotsocks;
struct sys_pollfd_nt sockfds[64];
struct timespec deadline, remain, now;
uint32_t cm, fi, wi, sn, pn, avail, waitfor, already_slept;
waitfor = ms ? *ms : -1u;
deadline = timespec_add(sys_poll_nt_now(), timespec_frommillis(waitfor));
// ensure revents is cleared
for (i = 0; i < nfds; ++i)
fds[i].revents = 0;
@ -171,7 +147,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
rc += !!fds[i].revents;
}
__fds_unlock();
if (rc)
if (rc == -1)
return rc;
// perform poll operation
@ -191,10 +167,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
if ((ev & POLLWRNORM_) && !(ev & POLLRDNORM_)) {
fds[fi].revents = fds[fi].events & (POLLRDNORM_ | POLLWRNORM_);
} else if (GetFileType(filehands[i]) == kNtFileTypePipe) {
ok = PeekNamedPipe(filehands[i], 0, 0, 0, &avail, 0);
POLLTRACE("PeekNamedPipe(%ld, 0, 0, 0, [%'u], 0) → {%hhhd, %d}",
filehands[i], avail, ok, GetLastError());
if (ok) {
if (PeekNamedPipe(filehands[i], 0, 0, 0, &avail, 0)) {
if (avail)
fds[fi].revents = POLLRDNORM_;
} else if (GetLastError() == kNtErrorHandleEof ||
@ -222,15 +195,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
}
// determine how long to wait
now = sys_poll_nt_now();
if (timespec_cmp(now, deadline) < 0) {
remain = timespec_sub(deadline, now);
millis = timespec_tomillis(remain);
waitfor = MIN(millis, 0xffffffffu);
waitfor = MIN(waitfor, POLL_INTERVAL_MS);
} else {
waitfor = 0; // we timed out
}
waitfor = sys_poll_nt_waitms(deadline);
// check for events and/or readiness on sockets
// we always do this due to issues with POLLOUT
@ -238,7 +203,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
// if we need to wait, then we prefer to wait inside WSAPoll()
// this ensures network events are received in ~10µs not ~10ms
if (!rc && waitfor) {
if (sys_poll_nt_sigcheck(sigmask))
if (__sigcheck(waitmask, false))
return -1;
already_slept = waitfor;
} else {
@ -253,7 +218,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
++rc;
}
} else if (already_slept) {
if (sys_poll_nt_sigcheck(sigmask))
if (__sigcheck(waitmask, false))
return -1;
}
} else {
@ -269,7 +234,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
// this ensures low latency for apps like emacs which with no sock
// here we shall actually report that something can be written too
if (!already_slept) {
if (sys_poll_nt_sigcheck(sigmask))
if (__sigcheck(waitmask, false))
return -1;
pt = _pthread_self();
filehands[pn] = pt->pt_semaphore = CreateSemaphore(0, 0, 1, 0);
@ -283,7 +248,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
return __winerr();
} else if (wi == pn) {
// our semaphore was signalled
if (sys_poll_nt_sigcheck(sigmask))
if (__sigcheck(waitmask, false))
return -1;
} else if ((wi ^ kNtWaitAbandoned) < pn) {
// this is possibly because a process or thread was killed
@ -328,7 +293,7 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
} else {
// should only be possible on kNtWaitTimeout or semaphore abandoned
// keep looping for events and we'll catch timeout when appropriate
if (sys_poll_nt_sigcheck(sigmask))
if (__sigcheck(waitmask, false))
return -1;
}
}
@ -341,11 +306,44 @@ textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
return rc;
}
textwindows static int sys_poll_nt_impl(struct pollfd *fds, uint64_t nfds,
struct timespec deadline,
const sigset_t waitmask) {
uint32_t waitms;
int i, n, rc, got = 0;
// fast path
if (nfds <= 63)
return sys_poll_nt_actual(fds, nfds, deadline, waitmask);
// clumsy path
for (;;) {
for (i = 0; i < nfds; i += 64) {
n = nfds - i;
n = n > 64 ? 64 : n;
rc = sys_poll_nt_actual(fds + i, n, timespec_zero, waitmask);
if (rc == -1)
return -1;
got += rc;
}
if (got)
return got;
if (!(waitms = sys_poll_nt_waitms(deadline)))
return 0;
if (_park_norestart(waitms, waitmask) == -1)
return -1;
}
}
textwindows int sys_poll_nt(struct pollfd *fds, uint64_t nfds, uint32_t *ms,
const sigset_t *sigmask) {
int rc;
struct timespec now, timeout, deadline;
BLOCK_SIGNALS;
rc = sys_poll_nt_impl(fds, nfds, ms, sigmask ? *sigmask : 0);
now = ms ? sys_poll_nt_now() : timespec_zero;
timeout = ms ? timespec_frommillis(*ms) : timespec_max;
deadline = timespec_add(now, timeout);
rc = sys_poll_nt_impl(fds, nfds, deadline, sigmask ? *sigmask : _SigMask);
ALLOW_SIGNALS;
return rc;
}

View file

@ -26,13 +26,6 @@
* should just create a separate thread for each client. poll() isn't a
* scalable i/o solution on any platform.
*
* On Windows it's only possible to poll 64 file descriptors at a time.
* This is a limitation imposed by WSAPoll(). Cosmopolitan Libc's poll()
* polyfill can go higher in some cases. For example, you can actually
* poll 64 sockets and 63 non-sockets 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,
@ -46,6 +39,12 @@
* 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.
*
* Your poll() function will check the status of all file descriptors
* before returning. This function won't block unless none of the fds
* had had any reportable status.
*
* The impact shutdown() will have on poll() is a dice roll across OSes.
*
* @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

View file

@ -156,11 +156,14 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout,
}
} else {
uint32_t ms;
if (!timeout ||
ckd_add(&ms, timeout->tv_sec, (timeout->tv_nsec + 999999) / 1000000)) {
ms = -1u;
uint32_t *msp;
if (timeout &&
!ckd_add(&ms, timeout->tv_sec, (timeout->tv_nsec + 999999) / 1000000)) {
msp = &ms;
} else {
msp = 0;
}
fdcount = sys_poll_nt(fds, nfds, &ms, sigmask);
fdcount = sys_poll_nt(fds, nfds, msp, sigmask);
}
if (IsOpenbsd() && fdcount != -1) {

View file

@ -384,12 +384,14 @@ textwindows static int ProcessMouseEvent(const struct NtInputRecord *r,
kNtLeftAltPressed | kNtRightAltPressed))) {
// we disable mouse highlighting when the tty is put in raw mode
// to mouse wheel events with widely understood vt100 arrow keys
*p++ = 033;
*p++ = !__keystroke.ohno_decckm ? '[' : 'O';
if (isup) {
*p++ = 'A';
} else {
*p++ = 'B';
for (int i = 0; i < 3; ++i) {
*p++ = 033;
*p++ = !__keystroke.ohno_decckm ? '[' : 'O';
if (isup) {
*p++ = 'A';
} else {
*p++ = 'B';
}
}
}
} else if ((bs || currentbs) && (__ttyconf.magic & kTtyXtMouse)) {

40
libc/calls/sigcheck.c Normal file
View file

@ -0,0 +1,40 @@
/*-*- 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/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/errfuns.h"
textwindows int __sigcheck(sigset_t waitmask, bool restartable) {
int sig, handler_was_called;
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);
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();
}
return 0;
}