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);
}
}