Fix many thread and file descriptor issues on Windows

The greenbean web server now works nearly perfectly on Windows with over
1000 threads. But some synchronization issues still remain which prevent
us from going over nine thousand.
This commit is contained in:
Justine Tunney 2022-05-20 18:51:41 -07:00
parent 96781d0679
commit 7838edae88
32 changed files with 363 additions and 192 deletions

View file

@ -48,26 +48,33 @@ textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) {
}
// allocate a new file descriptor
if (newfd == -1) {
if ((newfd = __reservefd_unlocked(start)) == -1) {
_spunlock(&__fds_lock);
return -1;
for (;;) {
if (newfd == -1) {
if ((newfd = __reservefd_unlocked(start)) == -1) {
_spunlock(&__fds_lock);
return -1;
}
break;
} else {
if (__ensurefds_unlocked(newfd) == -1) {
_spunlock(&__fds_lock);
return -1;
}
if (g_fds.p[newfd].kind) {
_spunlock(&__fds_lock);
close(newfd);
_spinlock(&__fds_lock);
}
if (!g_fds.p[newfd].kind) {
g_fds.p[newfd].kind = kFdReserved;
break;
}
}
} else {
if (__ensurefds_unlocked(newfd) == -1) {
_spunlock(&__fds_lock);
return -1;
}
if (g_fds.p[newfd].kind) {
_spunlock(&__fds_lock);
close(newfd);
_spinlock(&__fds_lock);
}
g_fds.p[newfd].kind = kFdReserved;
}
handle = g_fds.p[oldfd].handle;
proc = GetCurrentProcess();
if (DuplicateHandle(proc, handle, proc, &g_fds.p[newfd].handle, 0, true,
kNtDuplicateSameAccess)) {
g_fds.p[newfd].kind = g_fds.p[oldfd].kind;
@ -82,7 +89,7 @@ textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) {
}
rc = newfd;
} else {
__releasefd(newfd);
__releasefd_unlocked(newfd);
rc = __winerr();
}

View file

@ -66,7 +66,7 @@ struct Fd {
};
struct Fds {
size_t f; /* lowest free slot */
int f; /* lowest free slot */
size_t n; /* monotonic capacity */
struct Fd *p;
struct Fd __init_p[OPEN_MAX];

View file

@ -23,16 +23,35 @@
#include "libc/calls/strace.internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/dce.h"
#include "libc/intrin/spinlock.h"
#include "libc/intrin/lockcmpxchgp.h"
#include "libc/nexgen32e/threaded.h"
_Alignas(64) static int rlock;
static privileged inline bool AcquireInterruptPollLock(void) {
// any thread can poll for interrupts
// but it's wasteful to have every single thread doing it
int me, owner, tries;
if (!__threaded) return true;
me = gettid();
owner = 0;
if (_lockcmpxchgp(&rlock, &owner, me)) return true;
return owner == me;
}
static textwindows inline void ReleaseInterruptPollLock(void) {
int zero = 0;
__atomic_store(&rlock, &zero, __ATOMIC_RELAXED);
}
textwindows bool _check_interrupts(bool restartable, struct Fd *fd) {
bool res;
if (__time_critical) return false;
if (_trylock(&__fds_lock)) return false;
if (!AcquireInterruptPollLock()) return false;
if (weaken(_check_sigalrm)) weaken(_check_sigalrm)();
if (weaken(_check_sigchld)) weaken(_check_sigchld)();
if (fd && weaken(_check_sigwinch)) weaken(_check_sigwinch)(fd);
res = weaken(__sig_check) && weaken(__sig_check)(restartable);
_spunlock(&__fds_lock);
ReleaseInterruptPollLock();
return res;
}

View file

@ -22,6 +22,7 @@
#include "libc/calls/strace.internal.h"
#include "libc/calls/struct/termios.h"
#include "libc/calls/struct/winsize.h"
#include "libc/intrin/spinlock.h"
#include "libc/log/log.h"
#include "libc/nt/console.h"
#include "libc/nt/enum/startf.h"
@ -31,13 +32,15 @@
#include "libc/sysv/errfuns.h"
textwindows int ioctl_tiocgwinsz_nt(struct Fd *fd, struct winsize *ws) {
int i, e;
int i, e, rc;
uint32_t mode;
struct Fd *fds[3];
struct NtStartupInfo startinfo;
struct NtConsoleScreenBufferInfoEx sbinfo;
rc = -1;
e = errno;
if (ws) {
_spinlock(&__fds_lock);
fds[0] = fd, fds[1] = g_fds.p + 1, fds[2] = g_fds.p + 0;
GetStartupInfo(&startinfo);
for (i = 0; i < ARRAYLEN(fds); ++i) {
@ -51,14 +54,16 @@ textwindows int ioctl_tiocgwinsz_nt(struct Fd *fd, struct winsize *ws) {
ws->ws_xpixel = 0;
ws->ws_ypixel = 0;
errno = e;
return 0;
rc = 0;
break;
} else if (startinfo.dwFlags & kNtStartfUsecountchars) {
ws->ws_col = startinfo.dwXCountChars;
ws->ws_row = startinfo.dwYCountChars;
ws->ws_xpixel = 0;
ws->ws_ypixel = 0;
errno = e;
return 0;
rc = 0;
break;
} else {
__winerr();
}
@ -69,8 +74,9 @@ textwindows int ioctl_tiocgwinsz_nt(struct Fd *fd, struct winsize *ws) {
ebadf();
}
}
_spunlock(&__fds_lock);
} else {
efault();
}
return -1;
return rc;
}

View file

@ -49,9 +49,11 @@ textwindows int sys_pipe_nt(int pipefd[2], unsigned flags) {
} else {
mode = kNtPipeTypeMessage | kNtPipeReadmodeMessage;
}
if ((hin = CreateNamedPipe(
pipename, kNtPipeAccessInbound | kNtFileFlagOverlapped, mode, 1,
PIPE_BUF, PIPE_BUF, 0, &kNtIsInheritable)) != -1) {
_spunlock(&__fds_lock);
hin = CreateNamedPipe(pipename, kNtPipeAccessInbound | kNtFileFlagOverlapped,
mode, 1, PIPE_BUF, PIPE_BUF, 0, &kNtIsInheritable);
_spinlock(&__fds_lock);
if (hin != -1) {
if ((hout = CreateFile(pipename, kNtGenericWrite, 0, &kNtIsInheritable,
kNtOpenExisting, kNtFileFlagOverlapped, 0)) != -1) {
g_fds.p[reader].kind = kFdFile;

View file

@ -29,33 +29,38 @@
#include "libc/str/str.h"
#include "libc/sysv/errfuns.h"
// XXX: until we can add read locks to all the code that uses g_fds.p
// (right now we only have write locks) we need to keep old copies
// of g_fds.p around after it's been extended, so that threads
// which are using an fd they de facto own can continue reading
static void FreeOldFdsArray(void *p) {
weaken(free)(p);
}
/**
* Grows file descriptor array memory if needed.
*/
int __ensurefds_unlocked(int fd) {
size_t n1, n2;
struct Fd *p1, *p2;
if (fd < g_fds.n) return fd;
STRACE("__ensurefds(%d) extending", fd);
if (!weaken(malloc)) return emfile();
p1 = g_fds.p;
n1 = g_fds.n;
if (fd >= n1) {
STRACE("__ensurefds(%d) extending", fd);
if (weaken(malloc)) {
// TODO(jart): we need a semaphore for this
p1 = g_fds.p;
n2 = fd + (fd >> 1);
if ((p2 = weaken(malloc)(n2 * sizeof(*p1)))) {
memcpy(p2, p1, n1 * sizeof(*p1));
g_fds.p = p2;
g_fds.n = n2;
if (p1 != g_fds.__init_p) {
__cxa_atexit(free, p1, 0);
}
} else {
fd = enomem();
}
} else {
fd = emfile();
}
if (p1 == g_fds.__init_p) {
if (!(p2 = weaken(malloc)(sizeof(g_fds.__init_p)))) return -1;
memcpy(p2, p1, sizeof(g_fds.__init_p));
g_fds.p = p1 = p2;
}
n2 = n1;
while (n2 <= fd) n2 *= 2;
if (!(p2 = weaken(malloc)(n2 * sizeof(*p1)))) return -1;
__cxa_atexit(FreeOldFdsArray, p1, 0);
memcpy(p2, p1, n1 * sizeof(*p1));
bzero(p2 + n1, (p2 + n2) - (p2 + n1));
g_fds.p = p2;
g_fds.n = n2;
return fd;
}
@ -74,7 +79,7 @@ int __ensurefds(int fd) {
*/
int __reservefd_unlocked(int start) {
int fd;
for (fd = g_fds.f; fd < g_fds.n; ++fd) {
for (fd = MAX(start, g_fds.f); fd < g_fds.n; ++fd) {
if (!g_fds.p[fd].kind) {
break;
}
@ -100,10 +105,10 @@ int __reservefd(int start) {
* Closes non-stdio file descriptors to free dynamic memory.
*/
static void FreeFds(void) {
int i;
NTTRACE("FreeFds()");
int i, keep = 3;
STRACE("FreeFds()");
_spinlock(&__fds_lock);
for (i = 3; i < g_fds.n; ++i) {
for (i = keep; i < g_fds.n; ++i) {
if (g_fds.p[i].kind) {
_spunlock(&__fds_lock);
close(i);
@ -111,8 +116,11 @@ static void FreeFds(void) {
}
}
if (g_fds.p != g_fds.__init_p) {
memcpy(g_fds.__init_p, g_fds.p, sizeof(*g_fds.p) * 3);
weaken(free)(g_fds.p);
bzero(g_fds.__init_p, sizeof(g_fds.__init_p));
memcpy(g_fds.__init_p, g_fds.p, sizeof(*g_fds.p) * keep);
if (weaken(free)) {
weaken(free)(g_fds.p);
}
g_fds.p = g_fds.__init_p;
g_fds.n = ARRAYLEN(g_fds.__init_p);
}

View file

@ -21,6 +21,7 @@
#include "libc/calls/sig.internal.h"
#include "libc/calls/strace.internal.h"
#include "libc/dce.h"
#include "libc/intrin/spinlock.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
@ -38,7 +39,10 @@ void _check_sigchld(void) {
int pids[64];
uint32_t i, n;
int64_t handles[64];
if (!(n = __sample_pids(pids, handles, true))) return;
_spinlock(&__fds_lock);
n = __sample_pids(pids, handles, true);
_spunlock(&__fds_lock);
if (!n) return;
i = WaitForMultipleObjects(n, handles, false, 0);
if (i == kNtWaitTimeout) return;
if (i == kNtWaitFailed) {
@ -53,8 +57,10 @@ void _check_sigchld(void) {
if (__sighandflags[SIGCHLD] & SA_NOCLDWAIT) {
STRACE("SIGCHILD SA_NOCLDWAIT fd=%d handle=%ld", pids[i], handles[i]);
CloseHandle(handles[i]);
__releasefd_unlocked(pids[i]);
__releasefd(pids[i]);
}
_spinlock(&__fds_lock);
g_fds.p[pids[i]].zombie = true;
_spunlock(&__fds_lock);
__sig_add(SIGCHLD, CLD_EXITED);
}