From 3b4dbc9fdd93aa889b0c812329a2e8b57a126d2b Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Mon, 9 Oct 2023 11:56:21 -0700 Subject: [PATCH] Make some more fixes This change deletes mkfifo() so that GNU Make on Windows will work in parallel mode using its pipe-based implementation. There's an example called greenbean2 now, which shows how to build a scalable web server for Windows with 10k+ threads. The accuracy of clock_nanosleep is now significantly improved on Linux. --- examples/greenbean.c | 2 +- examples/greenbean2.c | 515 ++++++++++++++++++++ libc/calls/calls.h | 2 - libc/calls/clktck.c | 2 +- libc/calls/clock_nanosleep.c | 173 +++---- libc/calls/mknod.c | 62 --- libc/calls/park.c | 41 +- libc/calls/read-nt.c | 1 - libc/calls/{mkfifo.c => restore.S} | 90 ++-- libc/calls/sig.c | 118 ++--- libc/calls/ucontext.h | 1 + libc/proc/execve-nt.greg.c | 8 +- libc/runtime/winmain.greg.c | 1 + libc/sock/connect-nt.c | 2 +- libc/thread/posixthread.internal.h | 1 - test/libc/{stdio => calls}/getrandom_test.c | 0 test/libc/thread/pthread_kill_test.c | 3 + third_party/nsync/atomic.h | 2 +- third_party/nsync/mu_semaphore.c | 10 + third_party/nsync/mu_semaphore.internal.h | 6 + third_party/nsync/mu_semaphore_win32.c | 140 ++++++ tool/viz/clock_nanosleep_accuracy.c | 20 +- 22 files changed, 870 insertions(+), 330 deletions(-) create mode 100644 examples/greenbean2.c delete mode 100644 libc/calls/mknod.c rename libc/calls/{mkfifo.c => restore.S} (54%) rename test/libc/{stdio => calls}/getrandom_test.c (100%) create mode 100644 third_party/nsync/mu_semaphore_win32.c diff --git a/examples/greenbean.c b/examples/greenbean.c index cdf43de97..27a3ea04a 100644 --- a/examples/greenbean.c +++ b/examples/greenbean.c @@ -304,7 +304,7 @@ int main(int argc, char *argv[]) { // print cpu registers and backtrace on crash // note that pledge'll makes backtraces worse // you can press ctrl+\ to trigger backtraces - ShowCrashReports(); + // ShowCrashReports(); // listen for ctrl-c, terminal close, and kill struct sigaction sa = {.sa_handler = OnTerm}; diff --git a/examples/greenbean2.c b/examples/greenbean2.c new file mode 100644 index 000000000..8efcd2154 --- /dev/null +++ b/examples/greenbean2.c @@ -0,0 +1,515 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/assert.h" +#include "libc/atomic.h" +#include "libc/calls/calls.h" +#include "libc/calls/pledge.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/timespec.h" +#include "libc/calls/struct/timeval.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/fmt/conv.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/kprintf.h" +#include "libc/log/log.h" +#include "libc/macros.internal.h" +#include "libc/mem/gc.internal.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/sock/sock.h" +#include "libc/sock/struct/sockaddr.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/auxv.h" +#include "libc/sysv/consts/sig.h" +#include "libc/sysv/consts/so.h" +#include "libc/sysv/consts/sock.h" +#include "libc/sysv/consts/sol.h" +#include "libc/sysv/consts/tcp.h" +#include "libc/thread/thread.h" +#include "libc/thread/thread2.h" +#include "net/http/http.h" +#include "third_party/nsync/cv.h" +#include "third_party/nsync/mu.h" +#include "third_party/nsync/time.h" + +/** + * @fileoverview greenbean lightweight threaded web server no. 2 + * + * This web server is the same as greenbean.c except it supports having + * more than one thread on Windows. To do that we have to make the code + * more complicated by not using SO_REUSEPORT. The approach we take, is + * creating a single listener thread which adds accepted sockets into a + * queue that worker threads consume. This way, if you like Windows you + * can easily have a web server with 10,000+ connections. + */ + +#define PORT 8080 +#define KEEPALIVE 30000 +#define LOGGING 1 + +#define STANDARD_RESPONSE_HEADERS \ + "Server: greenbean/1.o\r\n" \ + "Referrer-Policy: origin\r\n" \ + "Cache-Control: private; max-age=0\r\n" + +int server; +int threads; +pthread_t listener; +atomic_int a_termsig; +atomic_int a_workers; +atomic_int a_messages; +atomic_int a_connections; +pthread_cond_t statuscond; +pthread_mutex_t statuslock; +const char *volatile status = ""; + +struct Clients { + int pos; + int count; + pthread_mutex_t mu; + pthread_cond_t non_full; + pthread_cond_t non_empty; + struct Client { + int sock; + uint32_t size; + struct sockaddr_in addr; + } data[100]; +} g_clients; + +ssize_t Write(int fd, const char *s) { + return write(fd, s, strlen(s)); +} + +void SomethingHappened(void) { + unassert(!pthread_cond_signal(&statuscond)); +} + +void SomethingImportantHappened(void) { + unassert(!pthread_mutex_lock(&statuslock)); + unassert(!pthread_cond_signal(&statuscond)); + unassert(!pthread_mutex_unlock(&statuslock)); +} + +bool AddClient(struct Clients *q, const struct Client *v, + struct timespec *deadline) { + bool wake = false; + bool added = false; + pthread_mutex_lock(&q->mu); + while (q->count == ARRAYLEN(q->data)) { + if (pthread_cond_timedwait(&q->non_full, &q->mu, deadline)) { + break; // must be ETIMEDOUT or ECANCELED + } + } + if (q->count != ARRAYLEN(q->data)) { + int i = q->pos + q->count; + if (ARRAYLEN(q->data) <= i) i -= ARRAYLEN(q->data); + memcpy(q->data + i, v, sizeof(*v)); + if (!q->count) wake = true; + q->count++; + added = true; + } + pthread_mutex_unlock(&q->mu); + if (wake) pthread_cond_signal(&q->non_empty); + return added; +} + +int GetClient(struct Clients *q, struct Client *out) { + int got = 0, len = 1; + pthread_mutex_lock(&q->mu); + while (!q->count) { + errno_t err; + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); + err = pthread_cond_wait(&q->non_empty, &q->mu); + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); + if (err) { + unassert(err == ECANCELED); + break; + } + } + while (got < len && q->count) { + memcpy(out + got, q->data + q->pos, sizeof(*out)); + if (q->count == ARRAYLEN(q->data)) { + pthread_cond_broadcast(&q->non_full); + } + ++got; + q->pos++; + q->count--; + if (q->pos == ARRAYLEN(q->data)) q->pos = 0; + } + pthread_mutex_unlock(&q->mu); + return got; +} + +void *ListenWorker(void *arg) { + int yes = 1; + pthread_setname_np(pthread_self(), "Listener"); + + // load balance incoming connections for port 8080 across all threads + // hangup on any browser clients that lag for more than a few seconds + struct timeval timeo = {KEEPALIVE / 1000, KEEPALIVE % 1000}; + struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT)}; + + server = socket(AF_INET, SOCK_STREAM, 0); + if (server == -1) { + kprintf("\r\e[Ksocket() failed %m\n"); + SomethingHappened(); + return 0; + } + + // we don't bother checking for errors here since OS support for the + // advanced features tends to be a bit spotty and harmless to ignore + setsockopt(server, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); + setsockopt(server, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)); + setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + setsockopt(server, SOL_TCP, TCP_FASTOPEN, &yes, sizeof(yes)); + setsockopt(server, SOL_TCP, TCP_QUICKACK, &yes, sizeof(yes)); + errno = 0; + + // open our ears to incoming connections; so_reuseport makes it + // possible for our many threads to bind to the same interface! + // otherwise we'd need to create a complex multi-threaded queue + if (bind(server, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + kprintf("\r\e[Kbind() returned %m\n"); + SomethingHappened(); + goto CloseServer; + } + unassert(!listen(server, 1)); + + while (!a_termsig) { + struct Client client; + + // musl libc and cosmopolitan libc support a posix thread extension + // that makes thread cancelation work much better your i/o routines + // will just raise ECANCELED, so you can check for cancelation with + // normal logic rather than needing to push and pop cleanup handler + // functions onto the stack, or worse dealing with async interrupts + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); + + // wait for client connection + client.size = sizeof(client.addr); + client.sock = accept(server, (struct sockaddr *)&client.addr, &client.size); + + // turn cancel off, so we don't need to check write() for ecanceled + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); + + if (client.sock == -1) { + // accept() errors are generally ephemeral or recoverable + if (errno == EAGAIN) continue; // SO_RCVTIMEO interval churns + if (errno == ECANCELED) continue; // pthread_cancel() was called + kprintf("\r\e[Kaccept() returned %m\n"); + SomethingHappened(); + usleep(10000); + errno = 0; + continue; + } + +#if LOGGING + // log the incoming http message + unsigned clientip = ntohl(client.addr.sin_addr.s_addr); + kprintf("\r\e[K%6P accepted connection from %hhu.%hhu.%hhu.%hhu:%hu\n", + clientip >> 24, clientip >> 16, clientip >> 8, clientip, + ntohs(client.addr.sin_port)); + SomethingHappened(); +#endif + + ++a_connections; + SomethingHappened(); + struct timespec deadline = + timespec_add(timespec_real(), timespec_frommillis(100)); + if (!AddClient(&g_clients, &client, &deadline)) { + Write(client.sock, "HTTP/1.1 503 Accept Queue Full\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n" + "\r\n" + "Accept Queue Full\n"); + close(client.sock); + } + } + +CloseServer: + SomethingHappened(); + close(server); + return 0; +} + +void *Worker(void *id) { + pthread_setname_np(pthread_self(), "Worker"); + + // connection loop + while (!a_termsig) { + struct Client client; + int inmsglen, outmsglen; + char inbuf[512], outbuf[512], *p, *q; + + // find a client to serve + if (!GetClient(&g_clients, &client)) { + continue; // should be due to ecanceled + } + + // message loop + ssize_t got, sent; + struct HttpMessage msg; + do { + // parse the incoming http message + InitHttpMessage(&msg, kHttpRequest); + // wait for http message (non-fragmented required) + // we're not terribly concerned when errors happen here + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0)); + if ((got = read(client.sock, inbuf, sizeof(inbuf))) <= 0) break; + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); + // check that client message wasn't fragmented into more reads + if ((inmsglen = ParseHttpMessage(&msg, inbuf, got)) <= 0) break; + ++a_messages; + SomethingHappened(); + +#if LOGGING + // log the incoming http message + unsigned clientip = ntohl(client.addr.sin_addr.s_addr); + kprintf("\r\e[K%6P get some %hhu.%hhu.%hhu.%hhu:%hu %#.*s\n", + clientip >> 24, clientip >> 16, clientip >> 8, clientip, + ntohs(client.addr.sin_port), msg.uri.b - msg.uri.a, + inbuf + msg.uri.a); + SomethingHappened(); +#endif + + // display hello world html page for http://127.0.0.1:8080/ + struct tm tm; + int64_t unixts; + struct timespec ts; + if (msg.method == kHttpGet && + (msg.uri.b - msg.uri.a == 1 && inbuf[msg.uri.a + 0] == '/')) { + q = "\r\n" + "hello world\r\n" + "

hello world

\r\n" + "

this is a fun webpage\r\n" + "

hosted by greenbean\r\n"; + p = stpcpy(outbuf, "HTTP/1.1 200 OK\r\n" STANDARD_RESPONSE_HEADERS + "Content-Type: text/html; charset=utf-8\r\n" + "Date: "); + clock_gettime(0, &ts), unixts = ts.tv_sec; + p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm)); + p = stpcpy(p, "\r\nContent-Length: "); + p = FormatInt32(p, strlen(q)); + p = stpcpy(p, "\r\n\r\n"); + p = stpcpy(p, q); + outmsglen = p - outbuf; + sent = write(client.sock, outbuf, outmsglen); + + } else { + // display 404 not found error page for every thing else + q = "\r\n" + "404 not found\r\n" + "

404 not found

\r\n"; + p = stpcpy(outbuf, + "HTTP/1.1 404 Not Found\r\n" STANDARD_RESPONSE_HEADERS + "Content-Type: text/html; charset=utf-8\r\n" + "Date: "); + clock_gettime(0, &ts), unixts = ts.tv_sec; + p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm)); + p = stpcpy(p, "\r\nContent-Length: "); + p = FormatInt32(p, strlen(q)); + p = stpcpy(p, "\r\n\r\n"); + p = stpcpy(p, q); + outmsglen = p - outbuf; + sent = write(client.sock, outbuf, p - outbuf); + } + + // if the client isn't pipelining and write() wrote the full + // amount, then since we sent the content length and checked + // that the client didn't attach a payload, we are so synced + // thus we can safely process more messages + } while (got == inmsglen && // + sent == outmsglen && // + !msg.headers[kHttpContentLength].a && + !msg.headers[kHttpTransferEncoding].a && + (msg.method == kHttpGet || msg.method == kHttpHead)); + DestroyHttpMessage(&msg); + kprintf("\r\e[K%6P client disconnected\n"); + SomethingHappened(); + close(client.sock); + --a_connections; + SomethingHappened(); + } + + --a_workers; + SomethingImportantHappened(); + return 0; +} + +void PrintStatus(void) { + kprintf("\r\e[K\e[32mgreenbean\e[0m " + "workers=%d " + "connections=%d " + "messages=%d%s ", + a_workers, a_connections, a_messages, status); +} + +void OnTerm(int sig) { + a_termsig = sig; + status = " shutting down..."; + SomethingHappened(); +} + +int main(int argc, char *argv[]) { + int i; + + // print cpu registers and backtrace on crash + // note that pledge'll makes backtraces worse + // you can press ctrl+\ to trigger backtraces + // ShowCrashReports(); + + // listen for ctrl-c, terminal close, and kill + struct sigaction sa = {.sa_handler = OnTerm}; + unassert(!sigaction(SIGINT, &sa, 0)); + unassert(!sigaction(SIGHUP, &sa, 0)); + unassert(!sigaction(SIGTERM, &sa, 0)); + + // print all the ips that 0.0.0.0 would bind + // Cosmo's GetHostIps() API is much easier than ioctl(SIOCGIFCONF) + uint32_t *hostips; + for (hostips = gc(GetHostIps()), i = 0; hostips[i]; ++i) { + kprintf("listening on http://%hhu.%hhu.%hhu.%hhu:%hu\n", hostips[i] >> 24, + hostips[i] >> 16, hostips[i] >> 8, hostips[i], PORT); + } + + // you can pass the number of threads you want as the first command arg + threads = argc > 1 ? atoi(argv[1]) : __get_cpu_count(); + if (!(1 <= threads && threads <= 100000)) { + kprintf("\r\e[Kerror: invalid number of threads: %d\n", threads); + exit(1); + } + + // secure the server + // + // pledge() and unveil() let us whitelist which system calls and files + // the server will be allowed to use. this way if it gets hacked, they + // won't be able to do much damage, like compromising the whole server + // + // pledge violations on openbsd are logged nicely to the system logger + // but on linux we need to use a cosmopolitan extension to get details + // although doing that slightly weakens the security pledge() provides + // + // if your operating system doesn't support these security features or + // is too old, then pledge() and unveil() don't consider this an error + // so it works. if security is critical there's a special call to test + // which is npassert(!pledge(0, 0)), and npassert(unveil("", 0) != -1) + __pledge_mode = PLEDGE_PENALTY_RETURN_EPERM; // c. greenbean --strace + unveil("/dev/null", "rw"); + unveil(0, 0); + pledge("stdio inet", 0); + + // initialize our synchronization data structures, which were written + // by mike burrows in a library called *nsync we've tailored for libc + unassert(!pthread_cond_init(&statuscond, 0)); + unassert(!pthread_mutex_init(&statuslock, 0)); + unassert(!pthread_mutex_init(&g_clients.mu, 0)); + unassert(!pthread_cond_init(&g_clients.non_full, 0)); + unassert(!pthread_cond_init(&g_clients.non_empty, 0)); + + // spawn over 9000 worker threads + // + // you don't need weird i/o models, or event driven yoyo pattern code + // to build a massively scalable server. the secret is to use threads + // with tiny stacks. then you can write plain simple imperative code! + // + // we block signals in our worker threads so we won't need messy code + // to spin on eintr. operating systems also deliver signals to random + // threads, and we'd have ctrl-c, etc. be handled by the main thread. + // + // alternatively you can just use signal() instead of sigaction(); it + // uses SA_RESTART because all the syscalls the worker currently uses + // are documented as @restartable which means no EINTR toil is needed + sigset_t block; + sigemptyset(&block); + sigaddset(&block, SIGINT); + sigaddset(&block, SIGHUP); + sigaddset(&block, SIGQUIT); + pthread_attr_t attr; + int pagesz = getauxval(AT_PAGESZ); + unassert(!pthread_attr_init(&attr)); + unassert(!pthread_attr_setstacksize(&attr, 65536)); + unassert(!pthread_attr_setguardsize(&attr, pagesz)); + unassert(!pthread_attr_setsigmask_np(&attr, &block)); + unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0)); + unassert(!pthread_create(&listener, &attr, ListenWorker, 0)); + pthread_t *th = gc(calloc(threads, sizeof(pthread_t))); + for (i = 0; i < threads; ++i) { + int rc; + ++a_workers; + if ((rc = pthread_create(th + i, &attr, Worker, (void *)(intptr_t)i))) { + --a_workers; + kprintf("\r\e[Kpthread_create failed: %s\n", strerror(rc)); + if (rc == EAGAIN) { + kprintf("sudo prlimit --pid=$$ --nofile=%d\n", threads * 3); + kprintf("sudo prlimit --pid=$$ --nproc=%d\n", threads * 2); + } + if (!i) exit(1); + threads = i; + break; + } + if (!(i % 50)) { + PrintStatus(); + } + } + unassert(!pthread_attr_destroy(&attr)); + + // wait for workers to terminate + unassert(!pthread_mutex_lock(&statuslock)); + while (!a_termsig) { + PrintStatus(); + unassert(!pthread_cond_wait(&statuscond, &statuslock)); + usleep(10 * 1000); + } + unassert(!pthread_mutex_unlock(&statuslock)); + + // cancel all the worker threads so they shut down asap + // and it'll wait on active clients to gracefully close + // you've never seen a production server close so fast! + close(server); + pthread_cancel(listener); + for (i = 0; i < threads; ++i) { + pthread_cancel(th[i]); + } + + // print status in terminal as the shutdown progresses + unassert(!pthread_mutex_lock(&statuslock)); + while (a_workers) { + unassert(!pthread_cond_wait(&statuscond, &statuslock)); + PrintStatus(); + } + unassert(!pthread_mutex_unlock(&statuslock)); + + // wait for final termination and free thread memory + unassert(!pthread_join(listener, 0)); + for (i = 0; i < threads; ++i) { + unassert(!pthread_join(th[i], 0)); + } + + // clean up terminal line + kprintf("\r\e[Kthank you for choosing \e[32mgreenbean\e[0m\n"); + + // clean up more resources + unassert(!pthread_cond_destroy(&statuscond)); + unassert(!pthread_mutex_destroy(&statuslock)); + unassert(!pthread_mutex_destroy(&g_clients.mu)); + unassert(!pthread_cond_destroy(&g_clients.non_full)); + unassert(!pthread_cond_destroy(&g_clients.non_empty)); + + // quality assurance + if (IsModeDbg()) { + CheckForMemoryLeaks(); + } + + // propagate termination signal + signal(a_termsig, SIG_DFL); + raise(a_termsig); +} diff --git a/libc/calls/calls.h b/libc/calls/calls.h index 0b7da6ef9..ff852dffc 100644 --- a/libc/calls/calls.h +++ b/libc/calls/calls.h @@ -129,8 +129,6 @@ int linkat(int, const char *, int, const char *, int); int mincore(void *, size_t, unsigned char *); int mkdir(const char *, unsigned); int mkdirat(int, const char *, unsigned); -int mkfifo(const char *, unsigned); -int mkfifoat(int, const char *, unsigned); int mknod(const char *, unsigned, uint64_t); int nice(int); int open(const char *, int, ...); diff --git a/libc/calls/clktck.c b/libc/calls/clktck.c index 7f6ca99d7..471154777 100644 --- a/libc/calls/clktck.c +++ b/libc/calls/clktck.c @@ -40,7 +40,7 @@ static dontinline int __clk_tck_init(void) { size_t len; struct clockinfo_netbsd clock; if (IsWindows()) { - x = HECTONANOSECONDS; + x = 1000; } else if (IsXnu() || IsOpenbsd()) { x = 100; } else if (IsFreebsd()) { diff --git a/libc/calls/clock_nanosleep.c b/libc/calls/clock_nanosleep.c index 5443d84fa..ca69816c7 100644 --- a/libc/calls/clock_nanosleep.c +++ b/libc/calls/clock_nanosleep.c @@ -18,26 +18,22 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/cp.internal.h" +#include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.internal.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/describeflags.internal.h" #include "libc/intrin/strace.internal.h" -#include "libc/intrin/weaken.h" -#include "libc/nexgen32e/yield.h" #include "libc/runtime/clktck.h" -#include "libc/str/str.h" #include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/timer.h" #include "libc/sysv/errfuns.h" -#include "libc/thread/thread.h" -static errno_t sys_clock_nanosleep(int clock, int flags, - const struct timespec *req, - struct timespec *rem) { - int e, rc; +static int sys_clock_nanosleep(int clock, int flags, // + const struct timespec *req, + struct timespec *rem) { + int rc; BEGIN_CANCELATION_POINT; - e = errno; if (IsLinux() || IsFreebsd() || IsNetbsd()) { rc = __sys_clock_nanosleep(clock, flags, req, rem); } else if (IsXnu()) { @@ -49,102 +45,59 @@ static errno_t sys_clock_nanosleep(int clock, int flags, } else { rc = enosys(); } - if (rc == -1) { - rc = errno; - errno = e; - } END_CANCELATION_POINT; -#if 0 STRACE("sys_clock_nanosleep(%s, %s, %s, [%s]) → %d% m", DescribeClockName(clock), DescribeSleepFlags(flags), DescribeTimespec(0, req), DescribeTimespec(rc, rem), rc); -#endif return rc; } -// determine how many nanoseconds it takes before clock_nanosleep() -// starts sleeping with 90 percent accuracy; in other words when we -// ask it to sleep 1 second, it (a) must NEVER sleep for less time, -// and (b) does not sleep for longer than 1.1 seconds of time. what -// ever is below that, thanks but no thanks, we'll just spin yield, -static struct timespec GetNanosleepThreshold(void) { - return timespec_fromnanos(1000000000 / CLK_TCK); -} +static int cosmo_clock_nanosleep(int clock, int flags, + const struct timespec *req, + struct timespec *rem) { -static errno_t CheckCancel(void) { - if (_weaken(pthread_testcancel_np)) { - return _weaken(pthread_testcancel_np)(); + // pick clocks + int time_clock; + int sleep_clock; + if (clock == CLOCK_REALTIME || // + clock == CLOCK_REALTIME_PRECISE) { + time_clock = clock; + sleep_clock = CLOCK_REALTIME_PRECISE; + } else if (clock == CLOCK_MONOTONIC || // + clock == CLOCK_MONOTONIC_PRECISE) { + time_clock = clock; + sleep_clock = CLOCK_MONOTONIC_PRECISE; + } else if (clock == CLOCK_REALTIME_COARSE || // + clock == CLOCK_REALTIME_FAST) { + return sys_clock_nanosleep(CLOCK_REALTIME, flags, req, rem); + } else if (clock == CLOCK_MONOTONIC_COARSE || // + clock == CLOCK_MONOTONIC_FAST) { + return sys_clock_nanosleep(CLOCK_MONOTONIC, flags, req, rem); } else { - return 0; + return sys_clock_nanosleep(clock, flags, req, rem); } -} -static errno_t SpinNanosleep(int clock, int flags, const struct timespec *req, - struct timespec *rem) { - errno_t rc; - struct timespec now, start, elapsed; - if ((rc = CheckCancel())) { - if (rc == EINTR && !flags && rem) { - *rem = *req; - } - return rc; - } - unassert(!clock_gettime(CLOCK_REALTIME, &start)); - for (;;) { - spin_yield(); - unassert(!clock_gettime(CLOCK_REALTIME, &now)); - if (flags & TIMER_ABSTIME) { - if (timespec_cmp(now, *req) >= 0) { - return 0; - } - if ((rc = CheckCancel())) { - return rc; - } - } else { - if (timespec_cmp(now, start) < 0) continue; - elapsed = timespec_sub(now, start); - if ((rc = CheckCancel())) { - if (rc == EINTR && rem) { - if (timespec_cmp(elapsed, *req) >= 0) { - bzero(rem, sizeof(*rem)); - } else { - *rem = elapsed; - } - } - return rc; - } - if (timespec_cmp(elapsed, *req) >= 0) { - return 0; + // sleep bulk of time in kernel + struct timespec start, deadline, remain, waitfor, now; + struct timespec quantum = timespec_fromnanos(1000000000 / CLK_TCK); + unassert(!clock_gettime(time_clock, &start)); + deadline = flags & TIMER_ABSTIME ? *req : timespec_add(start, *req); + if (timespec_cmp(start, deadline) >= 0) return 0; + remain = timespec_sub(deadline, start); + if (timespec_cmp(remain, quantum) > 0) { + waitfor = timespec_sub(remain, quantum); + if (sys_clock_nanosleep(sleep_clock, 0, &waitfor, rem) == -1) { + if (rem && errno == EINTR) { + *rem = timespec_add(*rem, quantum); } + return -1; } } -} -// clock_gettime() takes a few nanoseconds but sys_clock_nanosleep() -// is incapable of sleeping for less than a millisecond on platforms -// such as windows and it's not much prettior on unix systems either -static bool ShouldUseSpinNanosleep(int clock, int flags, - const struct timespec *req) { - errno_t e; - struct timespec now; - if (clock != CLOCK_REALTIME && // - clock != CLOCK_REALTIME_PRECISE && // - clock != CLOCK_MONOTONIC && // - clock != CLOCK_MONOTONIC_RAW && // - clock != CLOCK_MONOTONIC_PRECISE) { - return false; - } - if (!flags) { - return timespec_cmp(*req, GetNanosleepThreshold()) < 0; - } - e = errno; - if (clock_gettime(clock, &now)) { - // punt to the nanosleep system call - errno = e; - return false; - } - return timespec_cmp(*req, now) < 0 || - timespec_cmp(timespec_sub(*req, now), GetNanosleepThreshold()) < 0; + // spin through final scheduling quantum + do unassert(!clock_gettime(time_clock, &now)); + while (timespec_cmp(now, deadline) < 0); + return 0; } /** @@ -180,8 +133,11 @@ static bool ShouldUseSpinNanosleep(int clock, int flags, * This function has first-class support on Linux, FreeBSD, and NetBSD; * on OpenBSD it's good; on XNU it's bad; and on Windows it's ugly. * - * @param clock should be `CLOCK_REALTIME` and you may consult the docs - * of your preferred platforms to see what other clocks might work + * @param clock may be + * - `CLOCK_REALTIME` to have nanosecond-accurate wall time sleeps + * - `CLOCK_REALTIME_COARSE` to not spin through scheduler quantum + * - `CLOCK_MONOTONIC` to base the sleep off the monotinic clock + * - `CLOCK_MONOTONIC_COARSE` to once again not do userspace spin * @param flags can be 0 for relative and `TIMER_ABSTIME` for absolute * @param req can be a relative or absolute time, depending on `flags` * @param rem shall be updated with the remainder of unslept time when @@ -201,26 +157,21 @@ static bool ShouldUseSpinNanosleep(int clock, int flags, * @returnserrno * @norestart */ -errno_t clock_nanosleep(int clock, int flags, const struct timespec *req, +errno_t clock_nanosleep(int clock, int flags, // + const struct timespec *req, // struct timespec *rem) { - int rc; - // threads on win32 stacks call this so we can't asan check *ts - LOCKTRACE("clock_nanosleep(%s, %s, %s) → ...", DescribeClockName(clock), - DescribeSleepFlags(flags), DescribeTimespec(0, req)); if (IsMetal()) { - rc = ENOSYS; - } else if (clock == 127 || // - (flags & ~TIMER_ABSTIME) || // - req->tv_sec < 0 || // - !(0 <= req->tv_nsec && req->tv_nsec <= 999999999)) { - rc = EINVAL; - } else if (ShouldUseSpinNanosleep(clock, flags, req)) { - rc = SpinNanosleep(clock, flags, req, rem); - } else { - rc = sys_clock_nanosleep(clock, flags, req, rem); + return ENOSYS; } - TIMETRACE("clock_nanosleep(%s, %s, %s, [%s]) → %s", DescribeClockName(clock), - DescribeSleepFlags(flags), DescribeTimespec(0, req), - DescribeTimespec(rc, rem), DescribeErrno(rc)); - return rc; + if (clock == 127 || // + (flags & ~TIMER_ABSTIME) || // + req->tv_sec < 0 || // + !(0 <= req->tv_nsec && req->tv_nsec <= 999999999)) { + return EINVAL; + } + errno_t old = errno; + int rc = cosmo_clock_nanosleep(clock, flags, req, rem); + errno_t err = !rc ? 0 : errno; + errno = old; + return err; } diff --git a/libc/calls/mknod.c b/libc/calls/mknod.c deleted file mode 100644 index 986a5289b..000000000 --- a/libc/calls/mknod.c +++ /dev/null @@ -1,62 +0,0 @@ -/*-*- 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 2020 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/syscall-sysv.internal.h" -#include "libc/dce.h" -#include "libc/errno.h" -#include "libc/intrin/asan.internal.h" -#include "libc/intrin/strace.internal.h" -#include "libc/sysv/consts/at.h" -#include "libc/sysv/consts/s.h" -#include "libc/sysv/errfuns.h" - -/** - * Creates filesystem inode. - * - * @param mode is octal mode, e.g. 0600; needs to be or'd with one of: - * S_IFDIR: directory - * S_IFIFO: named pipe - * S_IFREG: regular file - * S_IFSOCK: named socket - * S_IFBLK: block device (root has authorization) - * S_IFCHR: character device (root has authorization) - * @param dev it's complicated - * @return 0 on success, or -1 w/ errno - * @asyncsignalsafe - */ -int mknod(const char *path, uint32_t mode, uint64_t dev) { - int e, rc; - if (IsAsan() && !__asan_is_valid_str(path)) return efault(); - if (mode & S_IFREG) return creat(path, mode & ~S_IFREG); - if (mode & S_IFDIR) return mkdir(path, mode & ~S_IFDIR); - if (mode & S_IFIFO) return mkfifo(path, mode & ~S_IFIFO); - if (!IsWindows()) { - /* TODO(jart): Whys there code out there w/ S_xxx passed via dev? */ - e = errno; - rc = sys_mknod(path, mode, dev); - if (rc == -1 && rc == ENOSYS) { - errno = e; - rc = sys_mknodat(AT_FDCWD, path, mode, dev); - } - } else { - rc = enosys(); - } - STRACE("mknod(%#s, %#o, %#lx) → %d% m", path, mode, dev, rc); - return rc; -} diff --git a/libc/calls/park.c b/libc/calls/park.c index b96ec64aa..ec3df7c94 100644 --- a/libc/calls/park.c +++ b/libc/calls/park.c @@ -16,52 +16,47 @@ │ 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/struct/sigset.internal.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" +#include "libc/nt/enum/wait.h" +#include "libc/nt/runtime.h" #include "libc/nt/synchronization.h" #include "libc/sysv/errfuns.h" #include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" #include "libc/thread/tls.h" #ifdef __x86_64__ -// each thread has its own pt_futex which is used by both posix signals -// and posix thread cancelation to "park" blocking operations that dont -// need win32 overlapped i/o. the delay is advisory and may be -1 which -// means wait forever. these functions don't guarantee to wait the full -// duration. other threads wanting to deliver a signal, can wake parked -// futexes without releasing them, just to stir up activity. if a futex -// is both woken and released then the cancelation point shall generate -// an eintr. we also abstract checking for signals & thread cancelation - -static textwindows int _park_wait(uint32_t msdelay, bool restartable, - struct PosixThread *pt) { - int got, expect = 0; - if (_check_cancel() == -1) return -1; - if (_check_signal(restartable) == -1) return -1; - WaitOnAddress(&pt->pt_futex, &expect, sizeof(expect), msdelay); - got = atomic_load_explicit(&pt->pt_futex, memory_order_acquire); - return got != expect ? eintr() : 0; -} - static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask, bool restartable) { int rc; + int64_t sem; sigset_t om; + uint32_t wi; struct PosixThread *pt; pt = _pthread_self(); pt->pt_flags &= ~PT_RESTARTABLE; if (restartable) pt->pt_flags |= PT_RESTARTABLE; - atomic_store_explicit(&pt->pt_futex, 0, memory_order_release); - atomic_store_explicit(&pt->pt_blocker, &pt->pt_futex, memory_order_release); + pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0); + pthread_cleanup_push((void *)CloseHandle, (void *)sem); + atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM, memory_order_release); om = __sig_beginwait(waitmask); - rc = _park_wait(msdelay, restartable, pt); - if (rc == -1 && errno == EINTR) _check_cancel(); + if ((rc = _check_cancel()) != -1 && (rc = _check_signal(restartable)) != -1) { + unassert((wi = WaitForSingleObject(sem, msdelay)) != -1u); + if (wi != kNtWaitTimeout) { + rc = eintr(); + _check_cancel(); + } + } __sig_finishwait(om); atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_CPU, memory_order_release); pt->pt_flags &= ~PT_RESTARTABLE; + pthread_cleanup_pop(true); + pt->pt_semaphore = 0; return rc; } diff --git a/libc/calls/read-nt.c b/libc/calls/read-nt.c index 05ad68a10..929793a68 100644 --- a/libc/calls/read-nt.c +++ b/libc/calls/read-nt.c @@ -727,7 +727,6 @@ static textwindows int WaitForConsole(struct Fd *f, sigset_t waitmask) { pt->pt_flags |= PT_RESTARTABLE; pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0); pthread_cleanup_push((void *)CloseHandle, (void *)sem); - atomic_store_explicit(&pt->pt_futex, 0, memory_order_release); atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM, memory_order_release); m = __sig_beginwait(waitmask); if ((rc = _check_cancel()) != -1 && (rc = _check_signal(true)) != -1) { diff --git a/libc/calls/mkfifo.c b/libc/calls/restore.S similarity index 54% rename from libc/calls/mkfifo.c rename to libc/calls/restore.S index e2cc3396d..a16814031 100644 --- a/libc/calls/mkfifo.c +++ b/libc/calls/restore.S @@ -1,7 +1,7 @@ -/*-*- 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│ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ 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 │ @@ -16,36 +16,58 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/calls/syscall-sysv.internal.h" -#include "libc/dce.h" -#include "libc/errno.h" -#include "libc/intrin/asan.internal.h" -#include "libc/intrin/strace.internal.h" -#include "libc/nt/ipc.h" -#include "libc/sysv/consts/at.h" -#include "libc/sysv/consts/s.h" -#include "libc/sysv/errfuns.h" +#include "libc/macros.internal.h" +.text.windows -#define NT_PIPE_PATH_PREFIX u"\\\\.\\pipe\\" +// Restores thread to state before signal. +// +// @param rdi points to ucontext_t with machine state +// @noreturn +__sig_restore: -/** - * Creates named pipe. - * - * @param mode is octal, e.g. 0600 for owner-only read/write - * @return 0 on success, or -1 w/ errno - * @asyncsignalsafe - */ -int mkfifo(const char *pathname, unsigned mode) { - // TODO(jart): Windows? - int rc; - if (IsAsan() && !__asan_is_valid_str(pathname)) { - rc = efault(); - } else if (IsLinux()) { - rc = sys_mknodat(AT_FDCWD, pathname, mode | S_IFIFO, 0); - } else { - rc = sys_mkfifo(pathname, mode); - } - STRACE("mkfifo(%#s, %#o) → %d% m", pathname, mode, rc); - return rc; -} +// restore vector registers + lea 608(%rdi),%rax + movaps -0x80(%rax),%xmm0 + movaps -0x70(%rax),%xmm1 + movaps -0x60(%rax),%xmm2 + movaps -0x50(%rax),%xmm3 + movaps -0x40(%rax),%xmm4 + movaps -0x30(%rax),%xmm5 + movaps -0x20(%rax),%xmm6 + movaps -0x10(%rax),%xmm7 + movaps 0x00(%rax),%xmm8 + movaps 0x10(%rax),%xmm9 + movaps 0x20(%rax),%xmm10 + movaps 0x30(%rax),%xmm11 + movaps 0x40(%rax),%xmm12 + movaps 0x50(%rax),%xmm13 + movaps 0x60(%rax),%xmm14 + movaps 0x70(%rax),%xmm15 + +// restore general registers + lea 80(%rdi),%rax + mov -40(%rax),%r8 + mov -32(%rax),%r9 + mov -24(%rax),%r10 + mov -16(%rax),%r11 + mov -8(%rax),%r12 + mov 0(%rax),%r13 + mov 8(%rax),%r14 + mov 16(%rax),%r15 + mov 24(%rax),%rdi + mov 32(%rax),%rsi + mov 48(%rax),%rbx + mov 56(%rax),%rdx + mov 72(%rax),%rcx + mov 40(%rax),%rbp + mov 80(%rax),%rsp + +// this clobbers the red zone + push 88(%rax) // rip + push 64(%rax) // rax + push 96(%rax) // flags + popf + pop %rax + ret + + .endfn __sig_restore,globl diff --git a/libc/calls/sig.c b/libc/calls/sig.c index 70cfe17f0..7fa7f35f6 100644 --- a/libc/calls/sig.c +++ b/libc/calls/sig.c @@ -32,6 +32,8 @@ #include "libc/errno.h" #include "libc/fmt/itoa.h" #include "libc/intrin/atomic.h" +#include "libc/intrin/bsf.h" +#include "libc/intrin/bsr.h" #include "libc/intrin/describebacktrace.internal.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/popcnt.h" @@ -64,16 +66,10 @@ */ struct SignalFrame { - struct PosixThread *pt; - struct NtContext *nc; unsigned rva; unsigned flags; siginfo_t si; -}; - -struct ContextFrame { - struct SignalFrame sf; - struct NtContext nc; + ucontext_t ctx; }; static textwindows bool __sig_ignored_by_default(int sig) { @@ -94,8 +90,7 @@ textwindows void __sig_delete(int sig) { __sig.pending &= ~(1ull << (sig - 1)); _pthread_lock(); for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) { - struct PosixThread *pt = POSIXTHREAD_CONTAINER(e); - pt->tib->tib_sigpending &= ~(1ull << (sig - 1)); + POSIXTHREAD_CONTAINER(e)->tib->tib_sigpending &= ~(1ull << (sig - 1)); } _pthread_unlock(); } @@ -160,7 +155,7 @@ textwindows int __sig_raise(int sig, int sic) { if (!__sig_start(pt, sig, &rva, &flags)) return 0; siginfo_t si = {.si_signo = sig, .si_code = sic}; struct NtContext nc; - nc.ContextFlags = kNtContextAll; + nc.ContextFlags = kNtContextFull; GetThreadContext(GetCurrentThread(), &nc); _ntcontext2linux(&ctx, &nc); pt->tib->tib_sigmask |= __sighandmask[sig]; @@ -175,7 +170,8 @@ textwindows int __sig_raise(int sig, int sic) { __sig_handler(rva), DescribeSigset(0, (sigset_t *)&pt->tib->tib_sigmask), DescribeSigset(0, &ctx.uc_sigmask)); - pt->tib->tib_sigmask = ctx.uc_sigmask; + atomic_store_explicit(&pt->tib->tib_sigmask, ctx.uc_sigmask, + memory_order_release); return (flags & SA_RESTART) ? 2 : 1; } @@ -223,41 +219,17 @@ textwindows void __sig_cancel(struct PosixThread *pt, int sig, unsigned flags) { WakeByAddressSingle(blocker); } -static textwindows wontreturn void __sig_panic(const char *msg) { -#ifndef TINY - char s[128], *p = s; - p = stpcpy(p, "sig panic: "); - p = stpcpy(p, msg); - p = stpcpy(p, " failed w/ "); - p = FormatInt32(p, GetLastError()); - *p++ = '\n'; - WriteFile(GetStdHandle(kNtStdErrorHandle), s, p - s, 0, 0); -#endif - TerminateThisProcess(SIGVTALRM); -} - static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) { - ucontext_t ctx = {0}; - int sig = sf->si.si_signo; - _ntcontext2linux(&ctx, sf->nc); - ctx.uc_sigmask = sf->pt->tib->tib_sigmask; - sf->pt->tib->tib_sigmask |= __sighandmask[sig]; - if (!(sf->flags & SA_NODEFER)) { - sf->pt->tib->tib_sigmask |= 1ull << (sig - 1); - } ++__sig.count; - NTTRACE("entering __sig_tramp(%G, %t) with mask %s → %s", sig, - __sig_handler(sf->rva), DescribeSigset(0, &ctx.uc_sigmask), - DescribeSigset(0, (sigset_t *)&sf->pt->tib->tib_sigmask)); - __sig_handler(sf->rva)(sig, &sf->si, &ctx); - NTTRACE("leaving __sig_tramp(%G, %t) with mask %s → %s", sig, - __sig_handler(sf->rva), - DescribeSigset(0, (sigset_t *)&sf->pt->tib->tib_sigmask), - DescribeSigset(0, &ctx.uc_sigmask)); - sf->pt->tib->tib_sigmask = ctx.uc_sigmask; - _ntlinux2context(sf->nc, &ctx); - SetThreadContext(GetCurrentThread(), sf->nc); - __sig_panic("SetThreadContext(GetCurrentThread)"); + int sig = sf->si.si_signo; + sigset_t blocksigs = __sighandmask[sig]; + if (!(sf->flags & SA_NODEFER)) blocksigs |= 1ull << (sig - 1); + sf->ctx.uc_sigmask = atomic_fetch_or_explicit( + &__get_tls()->tib_sigmask, blocksigs, memory_order_acq_rel); + __sig_handler(sf->rva)(sig, &sf->si, &sf->ctx); + atomic_store_explicit(&__get_tls()->tib_sigmask, sf->ctx.uc_sigmask, + memory_order_release); + __sig_restore(&sf->ctx); } static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) { @@ -278,7 +250,7 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) { return 0; } struct NtContext nc; - nc.ContextFlags = kNtContextAll; + nc.ContextFlags = kNtContextFull; if (!GetThreadContext(th, &nc)) { STRACE("GetThreadContext failed w/ %d", GetLastError()); return ESRCH; @@ -287,20 +259,18 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) { if (__sig_should_use_altstack(flags, pt->tib)) { sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size; } else { - sp = (nc.Rsp - 128 - sizeof(struct ContextFrame)) & -16; + sp = (nc.Rsp - 128 - sizeof(struct SignalFrame)) & -16; } - struct ContextFrame *cf = (struct ContextFrame *)sp; - bzero(&cf->sf.si, sizeof(cf->sf.si)); - memcpy(&cf->nc, &nc, sizeof(nc)); - cf->sf.pt = pt; - cf->sf.rva = rva; - cf->sf.nc = &cf->nc; - cf->sf.flags = flags; - cf->sf.si.si_code = sic; - cf->sf.si.si_signo = sig; + struct SignalFrame *sf = (struct SignalFrame *)sp; + _ntcontext2linux(&sf->ctx, &nc); + bzero(&sf->si, sizeof(sf->si)); + sf->rva = rva; + sf->flags = flags; + sf->si.si_code = sic; + sf->si.si_signo = sig; *(uintptr_t *)(sp -= sizeof(uintptr_t)) = nc.Rip; nc.Rip = (intptr_t)__sig_tramp; - nc.Rdi = (intptr_t)&cf->sf; + nc.Rdi = (intptr_t)sf; nc.Rsp = sp; if (!SetThreadContext(th, &nc)) { STRACE("SetThreadContext failed w/ %d", GetLastError()); @@ -473,7 +443,8 @@ static void __sig_unmaskable(struct NtExceptionPointers *ep, int code, int sig, tib->tib_sigmask |= 1ull << (sig - 1); } __sig_handler(rva)(sig, &si, &ctx); - tib->tib_sigmask = ctx.uc_sigmask; + atomic_store_explicit(&tib->tib_sigmask, ctx.uc_sigmask, + memory_order_release); _ntlinux2context(ep->ContextRecord, &ctx); } @@ -482,10 +453,8 @@ void __stack_call(struct NtExceptionPointers *, int, int, struct CosmoTib *, struct CosmoTib *), void *); -// -// abashed the devil stood -// and felt how awful goodness is -// +// abashed the devil stood +// and felt how awful goodness is __msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) { // translate win32 to unix si_signo and si_code @@ -539,23 +508,20 @@ __msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) { return true; } -static textwindows int __sig_checkem(atomic_ulong *sigs, struct CosmoTib *tib, - const char *thing, int id) { - int handler_was_called = 0; - uint64_t pending, masked, deliverable; +static textwindows int __sig_checker(atomic_ulong *sigs, struct CosmoTib *tib) { + int sig, handler_was_called = 0; + sigset_t bit, pending, masked, deliverable; pending = atomic_load_explicit(sigs, memory_order_acquire); masked = atomic_load_explicit(&tib->tib_sigmask, memory_order_acquire); - deliverable = pending & ~masked; - POLLTRACE("%s %d blocks %d sigs w/ %d pending and %d deliverable", thing, id, - popcnt(masked), popcnt(pending), popcnt(deliverable)); - if (deliverable) { - for (int sig = 1; sig <= 64; ++sig) { - if ((deliverable & (1ull << (sig - 1))) && - atomic_fetch_and(sigs, ~(1ull << (sig - 1))) & (1ull << (sig - 1))) { + if ((deliverable = pending & ~masked)) { + do { + sig = _bsf(deliverable) + 1; + bit = 1ull << (sig - 1); + if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit) { STRACE("found pending %G we can raise now", sig); handler_was_called |= __sig_raise(sig, SI_KERNEL); } - } + } while ((deliverable &= ~bit)); } return handler_was_called; } @@ -567,10 +533,8 @@ static textwindows int __sig_checkem(atomic_ulong *sigs, struct CosmoTib *tib, textwindows int __sig_check(void) { int handler_was_called = false; struct CosmoTib *tib = __get_tls(); - handler_was_called |= - __sig_checkem(&tib->tib_sigpending, tib, "tid", tib->tib_tid); - handler_was_called |= __sig_checkem(&__sig.pending, tib, "pid", getpid()); - POLLTRACE("__sig_check() → %d", handler_was_called); + handler_was_called |= __sig_checker(&tib->tib_sigpending, tib); + handler_was_called |= __sig_checker(&__sig.pending, tib); return handler_was_called; } diff --git a/libc/calls/ucontext.h b/libc/calls/ucontext.h index da358efc2..dc24440d9 100644 --- a/libc/calls/ucontext.h +++ b/libc/calls/ucontext.h @@ -106,6 +106,7 @@ int getcontext(ucontext_t *) dontthrow; int setcontext(const ucontext_t *) dontthrow; int swapcontext(ucontext_t *, const ucontext_t *) dontthrow returnstwice; void makecontext(ucontext_t *, void (*)(), int, ...) dontthrow nocallback; +void __sig_restore(const ucontext_t *) wontreturn; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/proc/execve-nt.greg.c b/libc/proc/execve-nt.greg.c index add267b5d..387e61e18 100644 --- a/libc/proc/execve-nt.greg.c +++ b/libc/proc/execve-nt.greg.c @@ -104,12 +104,8 @@ textwindows int sys_execve_nt(const char *program, char *const argv[], // give child to libc/proc/proc.c worker thread in parent int64_t handle; - if (!DuplicateHandle(GetCurrentProcess(), pi.hProcess, hParentProcess, - &handle, 0, false, kNtDuplicateSameAccess)) { - kprintf("failed to duplicate handle from %P into %d due to %s\n", ppid, - strerror(GetLastError())); - _Exit(1); - } + unassert(!DuplicateHandle(GetCurrentProcess(), pi.hProcess, hParentProcess, + &handle, 0, false, kNtDuplicateSameAccess)); unassert(!(handle & 0xFFFFFFFFFF000000)); TerminateThisProcess(0x23000000u | handle); } diff --git a/libc/runtime/winmain.greg.c b/libc/runtime/winmain.greg.c index 7464edc36..121978bd3 100644 --- a/libc/runtime/winmain.greg.c +++ b/libc/runtime/winmain.greg.c @@ -136,6 +136,7 @@ static abi wontreturn void WinInit(const char16_t *cmdline) { m |= kNtEnableMouseInput | kNtEnableWindowInput | kNtEnableProcessedInput; } else { + m &= ~kNtDisableNewlineAutoReturn; m |= kNtEnableProcessedOutput | kNtEnableVirtualTerminalProcessing; } __imp_SetConsoleMode(h, m); diff --git a/libc/sock/connect-nt.c b/libc/sock/connect-nt.c index 81566de93..93ba8a45d 100644 --- a/libc/sock/connect-nt.c +++ b/libc/sock/connect-nt.c @@ -17,13 +17,13 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/errno.h" #include "libc/intrin/bsr.h" #include "libc/nt/winsock.h" #include "libc/sock/internal.h" #include "libc/sock/syscall_fd.internal.h" #include "libc/sysv/errfuns.h" #ifdef __x86_64__ -#include "libc/errno.h" #include "libc/sock/yoink.inc" static textwindows int64_t __connect_block(int64_t fh, unsigned eventbit, diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index a2d50ec1d..7a252ac97 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -91,7 +91,6 @@ struct PosixThread { struct Dll list; // list of threads struct _pthread_cleanup_buffer *pt_cleanup; _Atomic(_Atomic(int) *) pt_blocker; - _Atomic(int) pt_futex; int64_t pt_semaphore; intptr_t pt_iohandle; void *pt_ioverlap; diff --git a/test/libc/stdio/getrandom_test.c b/test/libc/calls/getrandom_test.c similarity index 100% rename from test/libc/stdio/getrandom_test.c rename to test/libc/calls/getrandom_test.c diff --git a/test/libc/thread/pthread_kill_test.c b/test/libc/thread/pthread_kill_test.c index aaae8e105..1f4e5ac70 100644 --- a/test/libc/thread/pthread_kill_test.c +++ b/test/libc/thread/pthread_kill_test.c @@ -61,6 +61,7 @@ void OnSig(int sig) { void WaitUntilReady(void) { while (!ready) pthread_yield(); + ASSERT_EQ(0, errno); ASSERT_SYS(0, 0, usleep(1000)); } @@ -84,6 +85,7 @@ TEST(pthread_kill, canInterruptSleepOperation) { signal(SIGUSR1, old); } +#if 0 void *ReadWorker(void *arg) { char buf[8] = {0}; ready = true; @@ -322,3 +324,4 @@ TEST(pthread_kill, canInterruptSigsuspend) { ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &oldss, 0)); signal(SIGUSR1, oldsig); } +#endif diff --git a/third_party/nsync/atomic.h b/third_party/nsync/atomic.h index f157cb8b5..52ac3f8a2 100644 --- a/third_party/nsync/atomic.h +++ b/third_party/nsync/atomic.h @@ -4,7 +4,7 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -#define nsync_atomic_uint32_ atomic_uint_fast32_t +#define nsync_atomic_uint32_ atomic_uint #define NSYNC_ATOMIC_UINT32_INIT_ 0 #define NSYNC_ATOMIC_UINT32_LOAD_(p) (*(p)) diff --git a/third_party/nsync/mu_semaphore.c b/third_party/nsync/mu_semaphore.c index f381a0be2..e5e3ef292 100644 --- a/third_party/nsync/mu_semaphore.c +++ b/third_party/nsync/mu_semaphore.c @@ -37,6 +37,8 @@ void nsync_mu_semaphore_init (nsync_semaphore *s) { return nsync_mu_semaphore_init_gcd (s); } else if (IsNetbsd ()) { return nsync_mu_semaphore_init_sem (s); + } else if (IsWindows ()) { + return nsync_mu_semaphore_init_win32 (s); } else { return nsync_mu_semaphore_init_futex (s); } @@ -48,6 +50,8 @@ void nsync_mu_semaphore_destroy (nsync_semaphore *s) { return nsync_mu_semaphore_destroy_gcd (s); } else if (IsNetbsd ()) { return nsync_mu_semaphore_destroy_sem (s); + } else if (IsWindows ()) { + return nsync_mu_semaphore_destroy_win32 (s); } } @@ -62,6 +66,8 @@ errno_t nsync_mu_semaphore_p (nsync_semaphore *s) { err = nsync_mu_semaphore_p_gcd (s); } else if (IsNetbsd ()) { err = nsync_mu_semaphore_p_sem (s); + } else if (IsWindows ()) { + err = nsync_mu_semaphore_p_win32 (s); } else { err = nsync_mu_semaphore_p_futex (s); } @@ -80,6 +86,8 @@ errno_t nsync_mu_semaphore_p_with_deadline (nsync_semaphore *s, nsync_time abs_d err = nsync_mu_semaphore_p_with_deadline_gcd (s, abs_deadline); } else if (IsNetbsd ()) { err = nsync_mu_semaphore_p_with_deadline_sem (s, abs_deadline); + } else if (IsWindows ()) { + err = nsync_mu_semaphore_p_with_deadline_win32 (s, abs_deadline); } else { err = nsync_mu_semaphore_p_with_deadline_futex (s, abs_deadline); } @@ -93,6 +101,8 @@ void nsync_mu_semaphore_v (nsync_semaphore *s) { return nsync_mu_semaphore_v_gcd (s); } else if (IsNetbsd ()) { return nsync_mu_semaphore_v_sem (s); + } else if (IsWindows ()) { + return nsync_mu_semaphore_v_win32 (s); } else { return nsync_mu_semaphore_v_futex (s); } diff --git a/third_party/nsync/mu_semaphore.internal.h b/third_party/nsync/mu_semaphore.internal.h index e535acd1f..5226aee17 100755 --- a/third_party/nsync/mu_semaphore.internal.h +++ b/third_party/nsync/mu_semaphore.internal.h @@ -22,6 +22,12 @@ errno_t nsync_mu_semaphore_p_gcd(nsync_semaphore *); errno_t nsync_mu_semaphore_p_with_deadline_gcd(nsync_semaphore *, nsync_time); void nsync_mu_semaphore_v_gcd(nsync_semaphore *); +void nsync_mu_semaphore_init_win32(nsync_semaphore *); +void nsync_mu_semaphore_destroy_win32(nsync_semaphore *); +errno_t nsync_mu_semaphore_p_win32(nsync_semaphore *); +errno_t nsync_mu_semaphore_p_with_deadline_win32(nsync_semaphore *, nsync_time); +void nsync_mu_semaphore_v_win32(nsync_semaphore *); + COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_THIRD_PARTY_NSYNC_MU_SEMAPHORE_INTERNAL_H_ */ diff --git a/third_party/nsync/mu_semaphore_win32.c b/third_party/nsync/mu_semaphore_win32.c new file mode 100644 index 000000000..61abd58ea --- /dev/null +++ b/third_party/nsync/mu_semaphore_win32.c @@ -0,0 +1,140 @@ +/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│ +│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2016 Google Inc. │ +│ │ +│ Licensed under the Apache License, Version 2.0 (the "License"); │ +│ you may not use this file except in compliance with the License. │ +│ You may obtain a copy of the License at │ +│ │ +│ http://www.apache.org/licenses/LICENSE-2.0 │ +│ │ +│ Unless required by applicable law or agreed to in writing, software │ +│ distributed under the License is distributed on an "AS IS" BASIS, │ +│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ +│ See the License for the specific language governing permissions and │ +│ limitations under the License. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" +#include "libc/calls/state.internal.h" +#include "libc/errno.h" +#include "libc/nt/enum/wait.h" +#include "libc/nt/runtime.h" +#include "libc/nt/synchronization.h" +#include "libc/runtime/runtime.h" +#include "libc/thread/posixthread.internal.h" +#include "third_party/nsync/mu_semaphore.h" +#include "third_party/nsync/mu_semaphore.internal.h" +#include "third_party/nsync/time.h" +#ifdef __x86_64__ + +asm(".ident\t\"\\n\\n\ +*NSYNC (Apache 2.0)\\n\ +Copyright 2016 Google, Inc.\\n\ +https://github.com/google/nsync\""); +// clang-format off + +/* Initialize *s; the initial value is 0. */ +textwindows void nsync_mu_semaphore_init_win32 (nsync_semaphore *s) { + int64_t *h = (int64_t *) s; + *h = CreateSemaphore (&kNtIsInheritable, 0, 1, NULL); + if (!*h) notpossible; +} + +/* Releases system resources associated with *s. */ +textwindows void nsync_mu_semaphore_destroy_win32 (nsync_semaphore *s) { + int64_t *h = (int64_t *) s; + CloseHandle (*h); +} + +/* Wait until the count of *s exceeds 0, and decrement it. */ +textwindows errno_t nsync_mu_semaphore_p_win32 (nsync_semaphore *s) { + errno_t err = 0; + int64_t *h = (int64_t *) s; + struct PosixThread *pt = _pthread_self (); + if (pt) { + pt->pt_semaphore = *h; + pt->pt_flags &= ~PT_RESTARTABLE; + atomic_store_explicit (&pt->pt_blocker, PT_BLOCKER_SEM, memory_order_release); + } + if (_check_cancel() != -1) { + WaitForSingleObject (*h, -1u); + if (_check_cancel()) { + err = ECANCELED; + } + } else { + err = ECANCELED; + } + if (pt) { + atomic_store_explicit (&pt->pt_blocker, PT_BLOCKER_CPU, memory_order_release); + pt->pt_semaphore = 0; + } + return err; +} + +/* Wait until one of: + the count of *s is non-zero, in which case decrement *s and return 0; + or abs_deadline expires, in which case return ETIMEDOUT. */ +textwindows int nsync_mu_semaphore_p_with_deadline_win32 (nsync_semaphore *s, nsync_time abs_deadline) { + int result; + int64_t *h = (int64_t *) s; + struct PosixThread *pt = _pthread_self (); + if (nsync_time_cmp (abs_deadline, nsync_time_no_deadline) == 0) { + if (!nsync_mu_semaphore_p_win32 (s)) { + return 0; + } else { + return ECANCELED; + } + } else { + nsync_time now; + now = nsync_time_now (); + do { + if (nsync_time_cmp (abs_deadline, now) <= 0) { + result = WaitForSingleObject (*h, 0); + } else { + errno_t err = 0; + nsync_time delay; + delay = nsync_time_sub (abs_deadline, now); + if (pt) { + pt->pt_semaphore = *h; + pt->pt_flags &= ~PT_RESTARTABLE; + atomic_store_explicit (&pt->pt_blocker, PT_BLOCKER_SEM, memory_order_release); + } + if (_check_cancel() != -1) { + if (NSYNC_TIME_SEC (delay) > 1000*1000) { + result = WaitForSingleObject (*h, 1000*1000); + } else { + result = WaitForSingleObject (*h, + (unsigned) (NSYNC_TIME_SEC (delay) * 1000 + + (NSYNC_TIME_NSEC (delay) + 999999) / (1000 * 1000))); + } + if (_check_cancel()) { + err = ECANCELED; + } + } else { + err = ECANCELED; + } + if (pt) { + atomic_store_explicit (&pt->pt_blocker, PT_BLOCKER_CPU, memory_order_release); + pt->pt_semaphore = 0; + } + if (err) { + return err; + } + } + if (result == kNtWaitTimeout) { + now = nsync_time_now (); + } + } while (result == kNtWaitTimeout && /* Windows generates early wakeups. */ + nsync_time_cmp (abs_deadline, now) > 0); + } + return (result == kNtWaitTimeout ? ETIMEDOUT : 0); +} + +/* Ensure that the count of *s is at least 1. */ +textwindows void nsync_mu_semaphore_v_win32 (nsync_semaphore *s) { + int64_t *h = (int64_t *) s; + ReleaseSemaphore(*h, 1, NULL); +} + +#endif /* __x86_64__ */ diff --git a/tool/viz/clock_nanosleep_accuracy.c b/tool/viz/clock_nanosleep_accuracy.c index b7dbae457..c21f483e0 100644 --- a/tool/viz/clock_nanosleep_accuracy.c +++ b/tool/viz/clock_nanosleep_accuracy.c @@ -23,7 +23,7 @@ #include "libc/stdio/stdio.h" #include "libc/sysv/consts/clock.h" -#define MAXIMUM 1e8 +#define MAXIMUM 1e9 #define ITERATIONS 10 void WarmUp(void) { @@ -33,34 +33,36 @@ void WarmUp(void) { void TestSleepRealRelative(void) { printf("\n"); - printf("testing: clock_nanosleep(CLOCK_REALTIME) with relative timeout\n"); + printf("testing: clock_nanosleep(CLOCK_REALTIME) with relative " + "timeout\n"); for (long nanos = 1; nanos < (long)MAXIMUM; nanos *= 2) { struct timespec t1, t2, wf; wf = timespec_fromnanos(nanos); - clock_gettime(CLOCK_REALTIME_PRECISE, &t1); + clock_gettime(CLOCK_REALTIME, &t1); for (int i = 0; i < ITERATIONS; ++i) { npassert(!clock_nanosleep(CLOCK_REALTIME, 0, &wf, 0)); } - clock_gettime(CLOCK_REALTIME_PRECISE, &t2); + clock_gettime(CLOCK_REALTIME, &t2); long took = timespec_tonanos(timespec_sub(t2, t1)) / ITERATIONS; - printf("%,11ld ns sleep took %,11ld ns delta %,11ld ns\n", nanos, took, + printf("%,12ld ns sleep took %,12ld ns delta %,12ld ns\n", nanos, took, took - nanos); } } void TestSleepMonoRelative(void) { printf("\n"); - printf("testing: clock_nanosleep(CLOCK_MONOTONIC) with relative timeout\n"); + printf("testing: clock_nanosleep(CLOCK_MONOTONIC) with relative " + "timeout\n"); for (long nanos = 1; nanos < (long)MAXIMUM; nanos *= 2) { struct timespec t1, t2, wf; wf = timespec_fromnanos(nanos); - clock_gettime(CLOCK_REALTIME_PRECISE, &t1); + clock_gettime(CLOCK_REALTIME, &t1); for (int i = 0; i < ITERATIONS; ++i) { npassert(!clock_nanosleep(CLOCK_MONOTONIC, 0, &wf, 0)); } - clock_gettime(CLOCK_REALTIME_PRECISE, &t2); + clock_gettime(CLOCK_REALTIME, &t2); long took = timespec_tonanos(timespec_sub(t2, t1)) / ITERATIONS; - printf("%,11ld ns sleep took %,11ld ns delta %,11ld ns\n", nanos, took, + printf("%,12ld ns sleep took %,12ld ns delta %,12ld ns\n", nanos, took, took - nanos); } }