Improve threading and i/o routines

- On Windows connect() can now be interrupted by a signal; connect() w/
  O_NONBLOCK will now raise EINPROGRESS; and connect() with SO_SNDTIMEO
  will raise ETIMEDOUT after the interval has elapsed.

- We now get the AcceptEx(), ConnectEx(), and TransmitFile() functions
  from the WIN32 API the officially blessed way, using WSAIoctl().

- Do nothing on Windows when fsync() is called on a directory handle.
  This was raising EACCES earlier becaues GENERIC_WRITE is required on
  the handle. It's possible to FlushFileBuffers() a directory handle if
  it's opened with write access but MSDN doesn't document what it does.
  If you have any idea, please let us know!

- Prefer manual reset event objects for read() and write() on Windows.

- Do some code cleanup on our dlmalloc customizations.

- Fix errno type error in Windows blocking routines.

- Make the futex polyfill simpler and faster.
This commit is contained in:
Justine Tunney 2023-10-12 18:53:17 -07:00
parent f7343319cc
commit 49b0eaa69f
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
43 changed files with 528 additions and 425 deletions

View file

@ -17,29 +17,26 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/errno.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/cosmo.h"
#include "libc/nt/enum/wsaid.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/struct/sockaddr.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sock/wsaid.internal.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h"
#include "libc/thread/thread.h"
#ifdef __x86_64__
__msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
__msabi extern typeof(__sys_setsockopt_nt) *const __imp_setsockopt;
__msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
union AcceptExAddr {
struct sockaddr_storage addr;
@ -60,6 +57,21 @@ struct AcceptArgs {
struct AcceptExBuffer *buffer;
};
static struct {
atomic_uint once;
bool32 (*__msabi lpAcceptEx)(
int64_t sListenSocket, int64_t sAcceptSocket,
void *out_lpOutputBuffer /*[recvlen+local+remoteaddrlen]*/,
uint32_t dwReceiveDataLength, uint32_t dwLocalAddressLength,
uint32_t dwRemoteAddressLength, uint32_t *out_lpdwBytesReceived,
struct NtOverlapped *inout_lpOverlapped);
} g_acceptex;
static void acceptex_init(void) {
static struct NtGuid AcceptExGuid = WSAID_ACCEPTEX;
g_acceptex.lpAcceptEx = __get_wsaid(&AcceptExGuid);
}
static void sys_accept_nt_unwind(void *arg) {
struct AcceptResources *resources = arg;
if (resources->handle != -1) {
@ -70,9 +82,10 @@ static void sys_accept_nt_unwind(void *arg) {
static int sys_accept_nt_start(int64_t handle, struct NtOverlapped *overlap,
uint32_t *flags, void *arg) {
struct AcceptArgs *args = arg;
if (AcceptEx(args->listensock, handle, args->buffer, 0,
sizeof(args->buffer->local), sizeof(args->buffer->remote), 0,
overlap)) {
cosmo_once(&g_acceptex.once, acceptex_init);
if (g_acceptex.lpAcceptEx(args->listensock, handle, args->buffer, 0,
sizeof(args->buffer->local),
sizeof(args->buffer->remote), 0, overlap)) {
// inherit properties of listening socket
unassert(!__imp_setsockopt(args->listensock, SOL_SOCKET,
kNtSoUpdateAcceptContext, &handle,

View file

@ -16,20 +16,16 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/syscall_fd.internal.h"
#ifdef __x86_64__
__msabi extern typeof(__sys_bind_nt) *const __imp_bind;
textwindows int sys_bind_nt(struct Fd *fd, const void *addr,
uint32_t addrsize) {
unassert(fd->kind == kFdSocket);
if (__imp_bind(fd->handle, addr, addrsize) != -1) {
textwindows int sys_bind_nt(struct Fd *f, const void *addr, uint32_t addrsize) {
if (__imp_bind(f->handle, addr, addrsize) != -1) {
f->isbound = true;
return 0;
} else {
return __winsockerr();

View file

@ -16,6 +16,7 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/weaken.h"
#include "libc/mem/mem.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
@ -30,9 +31,11 @@ __msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
*
* This function should only be called by close().
*/
textwindows int sys_closesocket_nt(struct Fd *fd) {
int rc = __imp_closesocket(fd->handle);
if (rc != -1) {
textwindows int sys_closesocket_nt(struct Fd *f) {
if (_weaken(sys_connect_nt_cleanup)) {
_weaken(sys_connect_nt_cleanup)(f, true);
}
if (!__imp_closesocket(f->handle)) {
return 0;
} else {
return __winsockerr();

View file

@ -17,49 +17,133 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/cosmo.h"
#include "libc/errno.h"
#include "libc/intrin/bsr.h"
#include "libc/mem/mem.h"
#include "libc/nt/enum/wsaid.h"
#include "libc/nt/errors.h"
#include "libc/nt/struct/guid.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/struct/sockaddr.h"
#include "libc/sock/syscall_fd.internal.h"
#include "libc/sock/wsaid.internal.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
#include "libc/sock/yoink.inc"
static textwindows int64_t __connect_block(int64_t fh, unsigned eventbit,
int64_t rc, uint32_t timeout) {
int64_t eh;
struct NtWsaNetworkEvents ev;
if (rc != -1) return rc;
if (WSAGetLastError() != EWOULDBLOCK) return __winsockerr();
eh = WSACreateEvent();
bzero(&ev, sizeof(ev));
/* The proper way to reset the state of an event object used with the
WSAEventSelect function is to pass the handle of the event object
to the WSAEnumNetworkEvents function in the hEventObject parameter.
This will reset the event object and adjust the status of active FD
events on the socket in an atomic fashion. -- MSDN */
if (WSAEventSelect(fh, eh, 1u << eventbit) != -1 &&
WSAEnumNetworkEvents(fh, eh, &ev) != -1) {
if (!ev.iErrorCode[eventbit]) {
rc = 0;
} else {
errno = ev.iErrorCode[eventbit];
struct ConnectArgs {
const void *addr;
uint32_t addrsize;
};
static struct {
atomic_uint once;
bool32 (*__msabi lpConnectEx)(int64_t hSocket, const struct sockaddr *name,
int namelen, const void *opt_lpSendBuffer,
uint32_t dwSendDataLength,
uint32_t *opt_out_lpdwBytesSent,
struct NtOverlapped *lpOverlapped);
} g_connectex;
static void connectex_init(void) {
static struct NtGuid ConnectExGuid = WSAID_CONNECTEX;
g_connectex.lpConnectEx = __get_wsaid(&ConnectExGuid);
}
void sys_connect_nt_cleanup(struct Fd *f, bool cancel) {
struct NtOverlapped *overlap;
if ((overlap = f->connect_op)) {
uint32_t got, flags;
if (cancel) CancelIoEx(f->handle, overlap);
if (WSAGetOverlappedResult(f->handle, overlap, &got, cancel, &flags) ||
WSAGetLastError() != kNtErrorIoIncomplete) {
WSACloseEvent(overlap->hEvent);
free(overlap);
f->connect_op = 0;
}
} else {
__winsockerr();
}
WSACloseEvent(eh);
}
static int sys_connect_nt_start(int64_t hSocket,
struct NtOverlapped *lpOverlapped,
uint32_t *flags, void *arg) {
struct ConnectArgs *args = arg;
if (g_connectex.lpConnectEx(hSocket, args->addr, args->addrsize, 0, 0, 0,
lpOverlapped)) {
return 0;
} else {
return -1;
}
}
static textwindows int sys_connect_nt_impl(struct Fd *f, const void *addr,
uint32_t addrsize, sigset_t mask) {
// get connect function from winsock api
cosmo_once(&g_connectex.once, connectex_init);
// fail if previous connect() is still in progress
if (f->connect_op) return ealready();
// ConnectEx() requires bind() be called beforehand
if (!f->isbound) {
struct sockaddr_storage ss = {0};
ss.ss_family = ((struct sockaddr *)addr)->sa_family;
if (sys_bind_nt(f, &ss, sizeof(ss)) == -1) return -1;
}
// perform normal connect
if (!(f->flags & O_NONBLOCK)) {
ssize_t rc = __winsock_block(f->handle, 0, false, f->sndtimeo, mask,
sys_connect_nt_start,
&(struct ConnectArgs){addr, addrsize});
if (rc == -1 && errno == EAGAIN) {
// return ETIMEDOUT if SO_SNDTIMEO elapsed
// note that Linux will return EINPROGRESS
errno = etimedout();
}
return rc;
}
// perform nonblocking connect(), i.e.
// 1. connect(O_NONBLOCK) → EINPROGRESS
// 2. poll(POLLOUT)
bool32 ok;
struct NtOverlapped *overlap = calloc(1, sizeof(struct NtOverlapped));
if (!overlap) return -1;
overlap->hEvent = WSACreateEvent();
ok = g_connectex.lpConnectEx(f->handle, addr, addrsize, 0, 0, 0, overlap);
if (ok) {
uint32_t dwBytes, dwFlags;
ok = WSAGetOverlappedResult(f->handle, overlap, &dwBytes, false, &dwFlags);
WSACloseEvent(overlap->hEvent);
free(overlap);
return ok ? 0 : __winsockerr();
} else if (WSAGetLastError() == kNtErrorIoPending) {
f->connect_op = overlap;
return einprogress();
} else {
WSACloseEvent(overlap->hEvent);
free(overlap);
return __winsockerr();
}
}
textwindows int sys_connect_nt(struct Fd *f, const void *addr,
uint32_t addrsize) {
sigset_t mask = __sig_block();
int rc = sys_connect_nt_impl(f, addr, addrsize, mask);
__sig_unblock(mask);
return rc;
}
textwindows int sys_connect_nt(struct Fd *fd, const void *addr,
uint32_t addrsize) {
npassert(fd->kind == kFdSocket);
return __connect_block(
fd->handle, _bsr(kNtFdConnect),
WSAConnect(fd->handle, addr, addrsize, NULL, NULL, NULL, NULL),
fd->rcvtimeo);
}
#endif /* __x86_64__ */

View file

@ -17,27 +17,48 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/enum/wsaid.h"
#include "libc/nt/errors.h"
#include "libc/nt/events.h"
#include "libc/nt/files.h"
#include "libc/nt/struct/byhandlefileinformation.h"
#include "libc/nt/struct/guid.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/sock/internal.h"
#include "libc/sock/sendfile.internal.h"
#include "libc/sock/sock.h"
#include "libc/sock/wsaid.internal.h"
#include "libc/stdio/sysparam.h"
#include "libc/sysv/errfuns.h"
static struct {
atomic_uint once;
errno_t err;
bool32 (*__msabi lpTransmitFile)(
int64_t hSocket, int64_t hFile, uint32_t opt_nNumberOfBytesToWrite,
uint32_t opt_nNumberOfBytesPerSend,
struct NtOverlapped *opt_inout_lpOverlapped,
const struct NtTransmitFileBuffers *opt_lpTransmitBuffers,
uint32_t dwReserved);
} g_transmitfile;
static void transmitfile_init(void) {
static struct NtGuid TransmitfileGuid = WSAID_TRANSMITFILE;
g_transmitfile.lpTransmitFile = __get_wsaid(&TransmitfileGuid);
}
static dontinline textwindows ssize_t sys_sendfile_nt(
int outfd, int infd, int64_t *opt_in_out_inoffset, uint32_t uptobytes) {
ssize_t rc;
@ -64,7 +85,8 @@ static dontinline textwindows ssize_t sys_sendfile_nt(
}
BLOCK_SIGNALS;
struct NtOverlapped ov = {.hEvent = WSACreateEvent(), .Pointer = offset};
if (TransmitFile(oh, ih, uptobytes, 0, &ov, 0, 0) ||
cosmo_once(&g_transmitfile.once, transmitfile_init);
if (g_transmitfile.lpTransmitFile(oh, ih, uptobytes, 0, &ov, 0, 0) ||
WSAGetLastError() == kNtErrorIoPending ||
WSAGetLastError() == WSAEINPROGRESS) {
if (WSAGetOverlappedResult(oh, &ov, &uptobytes, true, &flags)) {

View file

@ -34,7 +34,6 @@ LIBC_SOCK_A_DIRECTDEPS = \
LIBC_NT_IPHLPAPI \
LIBC_NT_IPHLPAPI \
LIBC_NT_KERNEL32 \
LIBC_NT_MSWSOCK \
LIBC_NT_NTDLL \
LIBC_NT_WS2_32 \
LIBC_RUNTIME \

View file

@ -7,6 +7,7 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
void sys_connect_nt_cleanup(struct Fd *, bool);
int sys_accept_nt(struct Fd *, struct sockaddr_storage *, int);
int sys_bind_nt(struct Fd *, const void *, uint32_t);
int sys_closesocket_nt(struct Fd *);

View file

@ -69,10 +69,10 @@ __winsock_block(int64_t handle, uint32_t flags, bool nonblock,
uint64_t m;
uint32_t status;
uint32_t exchanged;
int olderror = errno;
bool eagained = false;
bool eintered = false;
bool canceled = false;
bool olderror = errno;
struct PosixThread *pt;
struct NtOverlapped overlap = {.hEvent = WSACreateEvent()};
struct WinsockBlockResources wbr = {handle, &overlap};

46
libc/sock/wsaid.c Normal file
View file

@ -0,0 +1,46 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 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/assert.h"
#include "libc/nt/enum/sio.h"
#include "libc/nt/struct/guid.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/sock/sock.h"
#include "libc/sock/wsaid.internal.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/sock.h"
__msabi extern typeof(__sys_closesocket_nt) *const __imp_closesocket;
// returns address of winsock function where msdn says we must do this
// this should be called once, since WSAIoctl has ~2 microsec overhead
void *__get_wsaid(const struct NtGuid *lpFunctionGuid) {
int r;
int64_t h;
void *lpFunc;
uint32_t dwBytes;
h = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
kNtWsaFlagOverlapped);
r = WSAIoctl(h, kNtSioGetExtensionFunctionPointer, lpFunctionGuid,
sizeof(struct NtGuid), &lpFunc, sizeof(lpFunc), &dwBytes, 0, 0);
unassert(r != -1);
__imp_closesocket(h);
return lpFunc;
}

View file

@ -0,0 +1,11 @@
#ifndef COSMOPOLITAN_LIBC_SOCK_WSAID_INTERNAL_H_
#define COSMOPOLITAN_LIBC_SOCK_WSAID_INTERNAL_H_
#include "libc/nt/struct/guid.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
void *__get_wsaid(const struct NtGuid *);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_SOCK_WSAID_INTERNAL_H_ */