mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-05-23 05:42:29 +00:00
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.
This commit is contained in:
parent
820c3599ed
commit
3b4dbc9fdd
22 changed files with 870 additions and 330 deletions
|
@ -304,7 +304,7 @@ int main(int argc, char *argv[]) {
|
||||||
// print cpu registers and backtrace on crash
|
// print cpu registers and backtrace on crash
|
||||||
// note that pledge'll makes backtraces worse
|
// note that pledge'll makes backtraces worse
|
||||||
// you can press ctrl+\ to trigger backtraces
|
// you can press ctrl+\ to trigger backtraces
|
||||||
ShowCrashReports();
|
// ShowCrashReports();
|
||||||
|
|
||||||
// listen for ctrl-c, terminal close, and kill
|
// listen for ctrl-c, terminal close, and kill
|
||||||
struct sigaction sa = {.sa_handler = OnTerm};
|
struct sigaction sa = {.sa_handler = OnTerm};
|
||||||
|
|
515
examples/greenbean2.c
Normal file
515
examples/greenbean2.c
Normal file
|
@ -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 = "<!doctype html>\r\n"
|
||||||
|
"<title>hello world</title>\r\n"
|
||||||
|
"<h1>hello world</h1>\r\n"
|
||||||
|
"<p>this is a fun webpage\r\n"
|
||||||
|
"<p>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 = "<!doctype html>\r\n"
|
||||||
|
"<title>404 not found</title>\r\n"
|
||||||
|
"<h1>404 not found</h1>\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);
|
||||||
|
}
|
|
@ -129,8 +129,6 @@ int linkat(int, const char *, int, const char *, int);
|
||||||
int mincore(void *, size_t, unsigned char *);
|
int mincore(void *, size_t, unsigned char *);
|
||||||
int mkdir(const char *, unsigned);
|
int mkdir(const char *, unsigned);
|
||||||
int mkdirat(int, 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 mknod(const char *, unsigned, uint64_t);
|
||||||
int nice(int);
|
int nice(int);
|
||||||
int open(const char *, int, ...);
|
int open(const char *, int, ...);
|
||||||
|
|
|
@ -40,7 +40,7 @@ static dontinline int __clk_tck_init(void) {
|
||||||
size_t len;
|
size_t len;
|
||||||
struct clockinfo_netbsd clock;
|
struct clockinfo_netbsd clock;
|
||||||
if (IsWindows()) {
|
if (IsWindows()) {
|
||||||
x = HECTONANOSECONDS;
|
x = 1000;
|
||||||
} else if (IsXnu() || IsOpenbsd()) {
|
} else if (IsXnu() || IsOpenbsd()) {
|
||||||
x = 100;
|
x = 100;
|
||||||
} else if (IsFreebsd()) {
|
} else if (IsFreebsd()) {
|
||||||
|
|
|
@ -18,26 +18,22 @@
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/assert.h"
|
#include "libc/assert.h"
|
||||||
#include "libc/calls/cp.internal.h"
|
#include "libc/calls/cp.internal.h"
|
||||||
|
#include "libc/calls/struct/timespec.h"
|
||||||
#include "libc/calls/struct/timespec.internal.h"
|
#include "libc/calls/struct/timespec.internal.h"
|
||||||
#include "libc/dce.h"
|
#include "libc/dce.h"
|
||||||
#include "libc/errno.h"
|
#include "libc/errno.h"
|
||||||
#include "libc/intrin/describeflags.internal.h"
|
#include "libc/intrin/describeflags.internal.h"
|
||||||
#include "libc/intrin/strace.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/runtime/clktck.h"
|
||||||
#include "libc/str/str.h"
|
|
||||||
#include "libc/sysv/consts/clock.h"
|
#include "libc/sysv/consts/clock.h"
|
||||||
#include "libc/sysv/consts/timer.h"
|
#include "libc/sysv/consts/timer.h"
|
||||||
#include "libc/sysv/errfuns.h"
|
#include "libc/sysv/errfuns.h"
|
||||||
#include "libc/thread/thread.h"
|
|
||||||
|
|
||||||
static errno_t sys_clock_nanosleep(int clock, int flags,
|
static int sys_clock_nanosleep(int clock, int flags, //
|
||||||
const struct timespec *req,
|
const struct timespec *req,
|
||||||
struct timespec *rem) {
|
struct timespec *rem) {
|
||||||
int e, rc;
|
int rc;
|
||||||
BEGIN_CANCELATION_POINT;
|
BEGIN_CANCELATION_POINT;
|
||||||
e = errno;
|
|
||||||
if (IsLinux() || IsFreebsd() || IsNetbsd()) {
|
if (IsLinux() || IsFreebsd() || IsNetbsd()) {
|
||||||
rc = __sys_clock_nanosleep(clock, flags, req, rem);
|
rc = __sys_clock_nanosleep(clock, flags, req, rem);
|
||||||
} else if (IsXnu()) {
|
} else if (IsXnu()) {
|
||||||
|
@ -49,102 +45,59 @@ static errno_t sys_clock_nanosleep(int clock, int flags,
|
||||||
} else {
|
} else {
|
||||||
rc = enosys();
|
rc = enosys();
|
||||||
}
|
}
|
||||||
if (rc == -1) {
|
|
||||||
rc = errno;
|
|
||||||
errno = e;
|
|
||||||
}
|
|
||||||
END_CANCELATION_POINT;
|
END_CANCELATION_POINT;
|
||||||
#if 0
|
|
||||||
STRACE("sys_clock_nanosleep(%s, %s, %s, [%s]) → %d% m",
|
STRACE("sys_clock_nanosleep(%s, %s, %s, [%s]) → %d% m",
|
||||||
DescribeClockName(clock), DescribeSleepFlags(flags),
|
DescribeClockName(clock), DescribeSleepFlags(flags),
|
||||||
DescribeTimespec(0, req), DescribeTimespec(rc, rem), rc);
|
DescribeTimespec(0, req), DescribeTimespec(rc, rem), rc);
|
||||||
#endif
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine how many nanoseconds it takes before clock_nanosleep()
|
static int cosmo_clock_nanosleep(int clock, int flags,
|
||||||
// starts sleeping with 90 percent accuracy; in other words when we
|
const struct timespec *req,
|
||||||
// ask it to sleep 1 second, it (a) must NEVER sleep for less time,
|
struct timespec *rem) {
|
||||||
// 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 errno_t CheckCancel(void) {
|
// pick clocks
|
||||||
if (_weaken(pthread_testcancel_np)) {
|
int time_clock;
|
||||||
return _weaken(pthread_testcancel_np)();
|
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 {
|
} else {
|
||||||
return 0;
|
return sys_clock_nanosleep(clock, flags, req, rem);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static errno_t SpinNanosleep(int clock, int flags, const struct timespec *req,
|
// sleep bulk of time in kernel
|
||||||
struct timespec *rem) {
|
struct timespec start, deadline, remain, waitfor, now;
|
||||||
errno_t rc;
|
struct timespec quantum = timespec_fromnanos(1000000000 / CLK_TCK);
|
||||||
struct timespec now, start, elapsed;
|
unassert(!clock_gettime(time_clock, &start));
|
||||||
if ((rc = CheckCancel())) {
|
deadline = flags & TIMER_ABSTIME ? *req : timespec_add(start, *req);
|
||||||
if (rc == EINTR && !flags && rem) {
|
if (timespec_cmp(start, deadline) >= 0) return 0;
|
||||||
*rem = *req;
|
remain = timespec_sub(deadline, start);
|
||||||
}
|
if (timespec_cmp(remain, quantum) > 0) {
|
||||||
return rc;
|
waitfor = timespec_sub(remain, quantum);
|
||||||
}
|
if (sys_clock_nanosleep(sleep_clock, 0, &waitfor, rem) == -1) {
|
||||||
unassert(!clock_gettime(CLOCK_REALTIME, &start));
|
if (rem && errno == EINTR) {
|
||||||
for (;;) {
|
*rem = timespec_add(*rem, quantum);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// clock_gettime() takes a few nanoseconds but sys_clock_nanosleep()
|
// spin through final scheduling quantum
|
||||||
// is incapable of sleeping for less than a millisecond on platforms
|
do unassert(!clock_gettime(time_clock, &now));
|
||||||
// such as windows and it's not much prettior on unix systems either
|
while (timespec_cmp(now, deadline) < 0);
|
||||||
static bool ShouldUseSpinNanosleep(int clock, int flags,
|
return 0;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,8 +133,11 @@ static bool ShouldUseSpinNanosleep(int clock, int flags,
|
||||||
* This function has first-class support on Linux, FreeBSD, and NetBSD;
|
* 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.
|
* 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
|
* @param clock may be
|
||||||
* of your preferred platforms to see what other clocks might work
|
* - `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 flags can be 0 for relative and `TIMER_ABSTIME` for absolute
|
||||||
* @param req can be a relative or absolute time, depending on `flags`
|
* @param req can be a relative or absolute time, depending on `flags`
|
||||||
* @param rem shall be updated with the remainder of unslept time when
|
* @param rem shall be updated with the remainder of unslept time when
|
||||||
|
@ -201,26 +157,21 @@ static bool ShouldUseSpinNanosleep(int clock, int flags,
|
||||||
* @returnserrno
|
* @returnserrno
|
||||||
* @norestart
|
* @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) {
|
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()) {
|
if (IsMetal()) {
|
||||||
rc = ENOSYS;
|
return 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);
|
|
||||||
}
|
}
|
||||||
TIMETRACE("clock_nanosleep(%s, %s, %s, [%s]) → %s", DescribeClockName(clock),
|
if (clock == 127 || //
|
||||||
DescribeSleepFlags(flags), DescribeTimespec(0, req),
|
(flags & ~TIMER_ABSTIME) || //
|
||||||
DescribeTimespec(rc, rem), DescribeErrno(rc));
|
req->tv_sec < 0 || //
|
||||||
return rc;
|
!(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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -16,52 +16,47 @@
|
||||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
|
#include "libc/assert.h"
|
||||||
#include "libc/calls/calls.h"
|
#include "libc/calls/calls.h"
|
||||||
#include "libc/calls/internal.h"
|
#include "libc/calls/internal.h"
|
||||||
#include "libc/calls/struct/sigset.internal.h"
|
#include "libc/calls/struct/sigset.internal.h"
|
||||||
#include "libc/errno.h"
|
#include "libc/errno.h"
|
||||||
#include "libc/intrin/atomic.h"
|
#include "libc/intrin/atomic.h"
|
||||||
|
#include "libc/nt/enum/wait.h"
|
||||||
|
#include "libc/nt/runtime.h"
|
||||||
#include "libc/nt/synchronization.h"
|
#include "libc/nt/synchronization.h"
|
||||||
#include "libc/sysv/errfuns.h"
|
#include "libc/sysv/errfuns.h"
|
||||||
#include "libc/thread/posixthread.internal.h"
|
#include "libc/thread/posixthread.internal.h"
|
||||||
|
#include "libc/thread/thread.h"
|
||||||
#include "libc/thread/tls.h"
|
#include "libc/thread/tls.h"
|
||||||
#ifdef __x86_64__
|
#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,
|
static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask,
|
||||||
bool restartable) {
|
bool restartable) {
|
||||||
int rc;
|
int rc;
|
||||||
|
int64_t sem;
|
||||||
sigset_t om;
|
sigset_t om;
|
||||||
|
uint32_t wi;
|
||||||
struct PosixThread *pt;
|
struct PosixThread *pt;
|
||||||
pt = _pthread_self();
|
pt = _pthread_self();
|
||||||
pt->pt_flags &= ~PT_RESTARTABLE;
|
pt->pt_flags &= ~PT_RESTARTABLE;
|
||||||
if (restartable) pt->pt_flags |= PT_RESTARTABLE;
|
if (restartable) pt->pt_flags |= PT_RESTARTABLE;
|
||||||
atomic_store_explicit(&pt->pt_futex, 0, memory_order_release);
|
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
|
||||||
atomic_store_explicit(&pt->pt_blocker, &pt->pt_futex, memory_order_release);
|
pthread_cleanup_push((void *)CloseHandle, (void *)sem);
|
||||||
|
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM, memory_order_release);
|
||||||
om = __sig_beginwait(waitmask);
|
om = __sig_beginwait(waitmask);
|
||||||
rc = _park_wait(msdelay, restartable, pt);
|
if ((rc = _check_cancel()) != -1 && (rc = _check_signal(restartable)) != -1) {
|
||||||
if (rc == -1 && errno == EINTR) _check_cancel();
|
unassert((wi = WaitForSingleObject(sem, msdelay)) != -1u);
|
||||||
|
if (wi != kNtWaitTimeout) {
|
||||||
|
rc = eintr();
|
||||||
|
_check_cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
__sig_finishwait(om);
|
__sig_finishwait(om);
|
||||||
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_CPU, memory_order_release);
|
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_CPU, memory_order_release);
|
||||||
pt->pt_flags &= ~PT_RESTARTABLE;
|
pt->pt_flags &= ~PT_RESTARTABLE;
|
||||||
|
pthread_cleanup_pop(true);
|
||||||
|
pt->pt_semaphore = 0;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -727,7 +727,6 @@ static textwindows int WaitForConsole(struct Fd *f, sigset_t waitmask) {
|
||||||
pt->pt_flags |= PT_RESTARTABLE;
|
pt->pt_flags |= PT_RESTARTABLE;
|
||||||
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
|
pt->pt_semaphore = sem = CreateSemaphore(0, 0, 1, 0);
|
||||||
pthread_cleanup_push((void *)CloseHandle, (void *)sem);
|
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);
|
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_SEM, memory_order_release);
|
||||||
m = __sig_beginwait(waitmask);
|
m = __sig_beginwait(waitmask);
|
||||||
if ((rc = _check_cancel()) != -1 && (rc = _check_signal(true)) != -1) {
|
if ((rc = _check_cancel()) != -1 && (rc = _check_signal(true)) != -1) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
|
||||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
│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 │
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||||
│ any purpose with or without fee is hereby granted, provided that the │
|
│ 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 │
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/calls/calls.h"
|
#include "libc/macros.internal.h"
|
||||||
#include "libc/calls/syscall-sysv.internal.h"
|
.text.windows
|
||||||
#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"
|
|
||||||
|
|
||||||
#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:
|
||||||
|
|
||||||
/**
|
// restore vector registers
|
||||||
* Creates named pipe.
|
lea 608(%rdi),%rax
|
||||||
*
|
movaps -0x80(%rax),%xmm0
|
||||||
* @param mode is octal, e.g. 0600 for owner-only read/write
|
movaps -0x70(%rax),%xmm1
|
||||||
* @return 0 on success, or -1 w/ errno
|
movaps -0x60(%rax),%xmm2
|
||||||
* @asyncsignalsafe
|
movaps -0x50(%rax),%xmm3
|
||||||
*/
|
movaps -0x40(%rax),%xmm4
|
||||||
int mkfifo(const char *pathname, unsigned mode) {
|
movaps -0x30(%rax),%xmm5
|
||||||
// TODO(jart): Windows?
|
movaps -0x20(%rax),%xmm6
|
||||||
int rc;
|
movaps -0x10(%rax),%xmm7
|
||||||
if (IsAsan() && !__asan_is_valid_str(pathname)) {
|
movaps 0x00(%rax),%xmm8
|
||||||
rc = efault();
|
movaps 0x10(%rax),%xmm9
|
||||||
} else if (IsLinux()) {
|
movaps 0x20(%rax),%xmm10
|
||||||
rc = sys_mknodat(AT_FDCWD, pathname, mode | S_IFIFO, 0);
|
movaps 0x30(%rax),%xmm11
|
||||||
} else {
|
movaps 0x40(%rax),%xmm12
|
||||||
rc = sys_mkfifo(pathname, mode);
|
movaps 0x50(%rax),%xmm13
|
||||||
}
|
movaps 0x60(%rax),%xmm14
|
||||||
STRACE("mkfifo(%#s, %#o) → %d% m", pathname, mode, rc);
|
movaps 0x70(%rax),%xmm15
|
||||||
return rc;
|
|
||||||
}
|
// 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
|
118
libc/calls/sig.c
118
libc/calls/sig.c
|
@ -32,6 +32,8 @@
|
||||||
#include "libc/errno.h"
|
#include "libc/errno.h"
|
||||||
#include "libc/fmt/itoa.h"
|
#include "libc/fmt/itoa.h"
|
||||||
#include "libc/intrin/atomic.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/describebacktrace.internal.h"
|
||||||
#include "libc/intrin/kprintf.h"
|
#include "libc/intrin/kprintf.h"
|
||||||
#include "libc/intrin/popcnt.h"
|
#include "libc/intrin/popcnt.h"
|
||||||
|
@ -64,16 +66,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct SignalFrame {
|
struct SignalFrame {
|
||||||
struct PosixThread *pt;
|
|
||||||
struct NtContext *nc;
|
|
||||||
unsigned rva;
|
unsigned rva;
|
||||||
unsigned flags;
|
unsigned flags;
|
||||||
siginfo_t si;
|
siginfo_t si;
|
||||||
};
|
ucontext_t ctx;
|
||||||
|
|
||||||
struct ContextFrame {
|
|
||||||
struct SignalFrame sf;
|
|
||||||
struct NtContext nc;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static textwindows bool __sig_ignored_by_default(int sig) {
|
static textwindows bool __sig_ignored_by_default(int sig) {
|
||||||
|
@ -94,8 +90,7 @@ textwindows void __sig_delete(int sig) {
|
||||||
__sig.pending &= ~(1ull << (sig - 1));
|
__sig.pending &= ~(1ull << (sig - 1));
|
||||||
_pthread_lock();
|
_pthread_lock();
|
||||||
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) {
|
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) {
|
||||||
struct PosixThread *pt = POSIXTHREAD_CONTAINER(e);
|
POSIXTHREAD_CONTAINER(e)->tib->tib_sigpending &= ~(1ull << (sig - 1));
|
||||||
pt->tib->tib_sigpending &= ~(1ull << (sig - 1));
|
|
||||||
}
|
}
|
||||||
_pthread_unlock();
|
_pthread_unlock();
|
||||||
}
|
}
|
||||||
|
@ -160,7 +155,7 @@ textwindows int __sig_raise(int sig, int sic) {
|
||||||
if (!__sig_start(pt, sig, &rva, &flags)) return 0;
|
if (!__sig_start(pt, sig, &rva, &flags)) return 0;
|
||||||
siginfo_t si = {.si_signo = sig, .si_code = sic};
|
siginfo_t si = {.si_signo = sig, .si_code = sic};
|
||||||
struct NtContext nc;
|
struct NtContext nc;
|
||||||
nc.ContextFlags = kNtContextAll;
|
nc.ContextFlags = kNtContextFull;
|
||||||
GetThreadContext(GetCurrentThread(), &nc);
|
GetThreadContext(GetCurrentThread(), &nc);
|
||||||
_ntcontext2linux(&ctx, &nc);
|
_ntcontext2linux(&ctx, &nc);
|
||||||
pt->tib->tib_sigmask |= __sighandmask[sig];
|
pt->tib->tib_sigmask |= __sighandmask[sig];
|
||||||
|
@ -175,7 +170,8 @@ textwindows int __sig_raise(int sig, int sic) {
|
||||||
__sig_handler(rva),
|
__sig_handler(rva),
|
||||||
DescribeSigset(0, (sigset_t *)&pt->tib->tib_sigmask),
|
DescribeSigset(0, (sigset_t *)&pt->tib->tib_sigmask),
|
||||||
DescribeSigset(0, &ctx.uc_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;
|
return (flags & SA_RESTART) ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,41 +219,17 @@ textwindows void __sig_cancel(struct PosixThread *pt, int sig, unsigned flags) {
|
||||||
WakeByAddressSingle(blocker);
|
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) {
|
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;
|
++__sig.count;
|
||||||
NTTRACE("entering __sig_tramp(%G, %t) with mask %s → %s", sig,
|
int sig = sf->si.si_signo;
|
||||||
__sig_handler(sf->rva), DescribeSigset(0, &ctx.uc_sigmask),
|
sigset_t blocksigs = __sighandmask[sig];
|
||||||
DescribeSigset(0, (sigset_t *)&sf->pt->tib->tib_sigmask));
|
if (!(sf->flags & SA_NODEFER)) blocksigs |= 1ull << (sig - 1);
|
||||||
__sig_handler(sf->rva)(sig, &sf->si, &ctx);
|
sf->ctx.uc_sigmask = atomic_fetch_or_explicit(
|
||||||
NTTRACE("leaving __sig_tramp(%G, %t) with mask %s → %s", sig,
|
&__get_tls()->tib_sigmask, blocksigs, memory_order_acq_rel);
|
||||||
__sig_handler(sf->rva),
|
__sig_handler(sf->rva)(sig, &sf->si, &sf->ctx);
|
||||||
DescribeSigset(0, (sigset_t *)&sf->pt->tib->tib_sigmask),
|
atomic_store_explicit(&__get_tls()->tib_sigmask, sf->ctx.uc_sigmask,
|
||||||
DescribeSigset(0, &ctx.uc_sigmask));
|
memory_order_release);
|
||||||
sf->pt->tib->tib_sigmask = ctx.uc_sigmask;
|
__sig_restore(&sf->ctx);
|
||||||
_ntlinux2context(sf->nc, &ctx);
|
|
||||||
SetThreadContext(GetCurrentThread(), sf->nc);
|
|
||||||
__sig_panic("SetThreadContext(GetCurrentThread)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
struct NtContext nc;
|
struct NtContext nc;
|
||||||
nc.ContextFlags = kNtContextAll;
|
nc.ContextFlags = kNtContextFull;
|
||||||
if (!GetThreadContext(th, &nc)) {
|
if (!GetThreadContext(th, &nc)) {
|
||||||
STRACE("GetThreadContext failed w/ %d", GetLastError());
|
STRACE("GetThreadContext failed w/ %d", GetLastError());
|
||||||
return ESRCH;
|
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)) {
|
if (__sig_should_use_altstack(flags, pt->tib)) {
|
||||||
sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size;
|
sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size;
|
||||||
} else {
|
} else {
|
||||||
sp = (nc.Rsp - 128 - sizeof(struct ContextFrame)) & -16;
|
sp = (nc.Rsp - 128 - sizeof(struct SignalFrame)) & -16;
|
||||||
}
|
}
|
||||||
struct ContextFrame *cf = (struct ContextFrame *)sp;
|
struct SignalFrame *sf = (struct SignalFrame *)sp;
|
||||||
bzero(&cf->sf.si, sizeof(cf->sf.si));
|
_ntcontext2linux(&sf->ctx, &nc);
|
||||||
memcpy(&cf->nc, &nc, sizeof(nc));
|
bzero(&sf->si, sizeof(sf->si));
|
||||||
cf->sf.pt = pt;
|
sf->rva = rva;
|
||||||
cf->sf.rva = rva;
|
sf->flags = flags;
|
||||||
cf->sf.nc = &cf->nc;
|
sf->si.si_code = sic;
|
||||||
cf->sf.flags = flags;
|
sf->si.si_signo = sig;
|
||||||
cf->sf.si.si_code = sic;
|
|
||||||
cf->sf.si.si_signo = sig;
|
|
||||||
*(uintptr_t *)(sp -= sizeof(uintptr_t)) = nc.Rip;
|
*(uintptr_t *)(sp -= sizeof(uintptr_t)) = nc.Rip;
|
||||||
nc.Rip = (intptr_t)__sig_tramp;
|
nc.Rip = (intptr_t)__sig_tramp;
|
||||||
nc.Rdi = (intptr_t)&cf->sf;
|
nc.Rdi = (intptr_t)sf;
|
||||||
nc.Rsp = sp;
|
nc.Rsp = sp;
|
||||||
if (!SetThreadContext(th, &nc)) {
|
if (!SetThreadContext(th, &nc)) {
|
||||||
STRACE("SetThreadContext failed w/ %d", GetLastError());
|
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);
|
tib->tib_sigmask |= 1ull << (sig - 1);
|
||||||
}
|
}
|
||||||
__sig_handler(rva)(sig, &si, &ctx);
|
__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);
|
_ntlinux2context(ep->ContextRecord, &ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,10 +453,8 @@ void __stack_call(struct NtExceptionPointers *, int, int, struct CosmoTib *,
|
||||||
struct CosmoTib *),
|
struct CosmoTib *),
|
||||||
void *);
|
void *);
|
||||||
|
|
||||||
//
|
// abashed the devil stood
|
||||||
// abashed the devil stood
|
// and felt how awful goodness is
|
||||||
// and felt how awful goodness is
|
|
||||||
//
|
|
||||||
__msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) {
|
__msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) {
|
||||||
|
|
||||||
// translate win32 to unix si_signo and si_code
|
// translate win32 to unix si_signo and si_code
|
||||||
|
@ -539,23 +508,20 @@ __msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static textwindows int __sig_checkem(atomic_ulong *sigs, struct CosmoTib *tib,
|
static textwindows int __sig_checker(atomic_ulong *sigs, struct CosmoTib *tib) {
|
||||||
const char *thing, int id) {
|
int sig, handler_was_called = 0;
|
||||||
int handler_was_called = 0;
|
sigset_t bit, pending, masked, deliverable;
|
||||||
uint64_t pending, masked, deliverable;
|
|
||||||
pending = atomic_load_explicit(sigs, memory_order_acquire);
|
pending = atomic_load_explicit(sigs, memory_order_acquire);
|
||||||
masked = atomic_load_explicit(&tib->tib_sigmask, memory_order_acquire);
|
masked = atomic_load_explicit(&tib->tib_sigmask, memory_order_acquire);
|
||||||
deliverable = pending & ~masked;
|
if ((deliverable = pending & ~masked)) {
|
||||||
POLLTRACE("%s %d blocks %d sigs w/ %d pending and %d deliverable", thing, id,
|
do {
|
||||||
popcnt(masked), popcnt(pending), popcnt(deliverable));
|
sig = _bsf(deliverable) + 1;
|
||||||
if (deliverable) {
|
bit = 1ull << (sig - 1);
|
||||||
for (int sig = 1; sig <= 64; ++sig) {
|
if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit) {
|
||||||
if ((deliverable & (1ull << (sig - 1))) &&
|
|
||||||
atomic_fetch_and(sigs, ~(1ull << (sig - 1))) & (1ull << (sig - 1))) {
|
|
||||||
STRACE("found pending %G we can raise now", sig);
|
STRACE("found pending %G we can raise now", sig);
|
||||||
handler_was_called |= __sig_raise(sig, SI_KERNEL);
|
handler_was_called |= __sig_raise(sig, SI_KERNEL);
|
||||||
}
|
}
|
||||||
}
|
} while ((deliverable &= ~bit));
|
||||||
}
|
}
|
||||||
return handler_was_called;
|
return handler_was_called;
|
||||||
}
|
}
|
||||||
|
@ -567,10 +533,8 @@ static textwindows int __sig_checkem(atomic_ulong *sigs, struct CosmoTib *tib,
|
||||||
textwindows int __sig_check(void) {
|
textwindows int __sig_check(void) {
|
||||||
int handler_was_called = false;
|
int handler_was_called = false;
|
||||||
struct CosmoTib *tib = __get_tls();
|
struct CosmoTib *tib = __get_tls();
|
||||||
handler_was_called |=
|
handler_was_called |= __sig_checker(&tib->tib_sigpending, tib);
|
||||||
__sig_checkem(&tib->tib_sigpending, tib, "tid", tib->tib_tid);
|
handler_was_called |= __sig_checker(&__sig.pending, tib);
|
||||||
handler_was_called |= __sig_checkem(&__sig.pending, tib, "pid", getpid());
|
|
||||||
POLLTRACE("__sig_check() → %d", handler_was_called);
|
|
||||||
return handler_was_called;
|
return handler_was_called;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ int getcontext(ucontext_t *) dontthrow;
|
||||||
int setcontext(const ucontext_t *) dontthrow;
|
int setcontext(const ucontext_t *) dontthrow;
|
||||||
int swapcontext(ucontext_t *, const ucontext_t *) dontthrow returnstwice;
|
int swapcontext(ucontext_t *, const ucontext_t *) dontthrow returnstwice;
|
||||||
void makecontext(ucontext_t *, void (*)(), int, ...) dontthrow nocallback;
|
void makecontext(ucontext_t *, void (*)(), int, ...) dontthrow nocallback;
|
||||||
|
void __sig_restore(const ucontext_t *) wontreturn;
|
||||||
|
|
||||||
COSMOPOLITAN_C_END_
|
COSMOPOLITAN_C_END_
|
||||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||||
|
|
|
@ -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
|
// give child to libc/proc/proc.c worker thread in parent
|
||||||
int64_t handle;
|
int64_t handle;
|
||||||
if (!DuplicateHandle(GetCurrentProcess(), pi.hProcess, hParentProcess,
|
unassert(!DuplicateHandle(GetCurrentProcess(), pi.hProcess, hParentProcess,
|
||||||
&handle, 0, false, kNtDuplicateSameAccess)) {
|
&handle, 0, false, kNtDuplicateSameAccess));
|
||||||
kprintf("failed to duplicate handle from %P into %d due to %s\n", ppid,
|
|
||||||
strerror(GetLastError()));
|
|
||||||
_Exit(1);
|
|
||||||
}
|
|
||||||
unassert(!(handle & 0xFFFFFFFFFF000000));
|
unassert(!(handle & 0xFFFFFFFFFF000000));
|
||||||
TerminateThisProcess(0x23000000u | handle);
|
TerminateThisProcess(0x23000000u | handle);
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,7 @@ static abi wontreturn void WinInit(const char16_t *cmdline) {
|
||||||
m |= kNtEnableMouseInput | kNtEnableWindowInput |
|
m |= kNtEnableMouseInput | kNtEnableWindowInput |
|
||||||
kNtEnableProcessedInput;
|
kNtEnableProcessedInput;
|
||||||
} else {
|
} else {
|
||||||
|
m &= ~kNtDisableNewlineAutoReturn;
|
||||||
m |= kNtEnableProcessedOutput | kNtEnableVirtualTerminalProcessing;
|
m |= kNtEnableProcessedOutput | kNtEnableVirtualTerminalProcessing;
|
||||||
}
|
}
|
||||||
__imp_SetConsoleMode(h, m);
|
__imp_SetConsoleMode(h, m);
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||||
#include "libc/assert.h"
|
#include "libc/assert.h"
|
||||||
|
#include "libc/errno.h"
|
||||||
#include "libc/intrin/bsr.h"
|
#include "libc/intrin/bsr.h"
|
||||||
#include "libc/nt/winsock.h"
|
#include "libc/nt/winsock.h"
|
||||||
#include "libc/sock/internal.h"
|
#include "libc/sock/internal.h"
|
||||||
#include "libc/sock/syscall_fd.internal.h"
|
#include "libc/sock/syscall_fd.internal.h"
|
||||||
#include "libc/sysv/errfuns.h"
|
#include "libc/sysv/errfuns.h"
|
||||||
#ifdef __x86_64__
|
#ifdef __x86_64__
|
||||||
#include "libc/errno.h"
|
|
||||||
#include "libc/sock/yoink.inc"
|
#include "libc/sock/yoink.inc"
|
||||||
|
|
||||||
static textwindows int64_t __connect_block(int64_t fh, unsigned eventbit,
|
static textwindows int64_t __connect_block(int64_t fh, unsigned eventbit,
|
||||||
|
|
|
@ -91,7 +91,6 @@ struct PosixThread {
|
||||||
struct Dll list; // list of threads
|
struct Dll list; // list of threads
|
||||||
struct _pthread_cleanup_buffer *pt_cleanup;
|
struct _pthread_cleanup_buffer *pt_cleanup;
|
||||||
_Atomic(_Atomic(int) *) pt_blocker;
|
_Atomic(_Atomic(int) *) pt_blocker;
|
||||||
_Atomic(int) pt_futex;
|
|
||||||
int64_t pt_semaphore;
|
int64_t pt_semaphore;
|
||||||
intptr_t pt_iohandle;
|
intptr_t pt_iohandle;
|
||||||
void *pt_ioverlap;
|
void *pt_ioverlap;
|
||||||
|
|
|
@ -61,6 +61,7 @@ void OnSig(int sig) {
|
||||||
|
|
||||||
void WaitUntilReady(void) {
|
void WaitUntilReady(void) {
|
||||||
while (!ready) pthread_yield();
|
while (!ready) pthread_yield();
|
||||||
|
ASSERT_EQ(0, errno);
|
||||||
ASSERT_SYS(0, 0, usleep(1000));
|
ASSERT_SYS(0, 0, usleep(1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +85,7 @@ TEST(pthread_kill, canInterruptSleepOperation) {
|
||||||
signal(SIGUSR1, old);
|
signal(SIGUSR1, old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
void *ReadWorker(void *arg) {
|
void *ReadWorker(void *arg) {
|
||||||
char buf[8] = {0};
|
char buf[8] = {0};
|
||||||
ready = true;
|
ready = true;
|
||||||
|
@ -322,3 +324,4 @@ TEST(pthread_kill, canInterruptSigsuspend) {
|
||||||
ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &oldss, 0));
|
ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &oldss, 0));
|
||||||
signal(SIGUSR1, oldsig);
|
signal(SIGUSR1, oldsig);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
2
third_party/nsync/atomic.h
vendored
2
third_party/nsync/atomic.h
vendored
|
@ -4,7 +4,7 @@
|
||||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||||
COSMOPOLITAN_C_START_
|
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_INIT_ 0
|
||||||
#define NSYNC_ATOMIC_UINT32_LOAD_(p) (*(p))
|
#define NSYNC_ATOMIC_UINT32_LOAD_(p) (*(p))
|
||||||
|
|
10
third_party/nsync/mu_semaphore.c
vendored
10
third_party/nsync/mu_semaphore.c
vendored
|
@ -37,6 +37,8 @@ void nsync_mu_semaphore_init (nsync_semaphore *s) {
|
||||||
return nsync_mu_semaphore_init_gcd (s);
|
return nsync_mu_semaphore_init_gcd (s);
|
||||||
} else if (IsNetbsd ()) {
|
} else if (IsNetbsd ()) {
|
||||||
return nsync_mu_semaphore_init_sem (s);
|
return nsync_mu_semaphore_init_sem (s);
|
||||||
|
} else if (IsWindows ()) {
|
||||||
|
return nsync_mu_semaphore_init_win32 (s);
|
||||||
} else {
|
} else {
|
||||||
return nsync_mu_semaphore_init_futex (s);
|
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);
|
return nsync_mu_semaphore_destroy_gcd (s);
|
||||||
} else if (IsNetbsd ()) {
|
} else if (IsNetbsd ()) {
|
||||||
return nsync_mu_semaphore_destroy_sem (s);
|
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);
|
err = nsync_mu_semaphore_p_gcd (s);
|
||||||
} else if (IsNetbsd ()) {
|
} else if (IsNetbsd ()) {
|
||||||
err = nsync_mu_semaphore_p_sem (s);
|
err = nsync_mu_semaphore_p_sem (s);
|
||||||
|
} else if (IsWindows ()) {
|
||||||
|
err = nsync_mu_semaphore_p_win32 (s);
|
||||||
} else {
|
} else {
|
||||||
err = nsync_mu_semaphore_p_futex (s);
|
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);
|
err = nsync_mu_semaphore_p_with_deadline_gcd (s, abs_deadline);
|
||||||
} else if (IsNetbsd ()) {
|
} else if (IsNetbsd ()) {
|
||||||
err = nsync_mu_semaphore_p_with_deadline_sem (s, abs_deadline);
|
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 {
|
} else {
|
||||||
err = nsync_mu_semaphore_p_with_deadline_futex (s, abs_deadline);
|
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);
|
return nsync_mu_semaphore_v_gcd (s);
|
||||||
} else if (IsNetbsd ()) {
|
} else if (IsNetbsd ()) {
|
||||||
return nsync_mu_semaphore_v_sem (s);
|
return nsync_mu_semaphore_v_sem (s);
|
||||||
|
} else if (IsWindows ()) {
|
||||||
|
return nsync_mu_semaphore_v_win32 (s);
|
||||||
} else {
|
} else {
|
||||||
return nsync_mu_semaphore_v_futex (s);
|
return nsync_mu_semaphore_v_futex (s);
|
||||||
}
|
}
|
||||||
|
|
6
third_party/nsync/mu_semaphore.internal.h
vendored
6
third_party/nsync/mu_semaphore.internal.h
vendored
|
@ -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);
|
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_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_
|
COSMOPOLITAN_C_END_
|
||||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||||
#endif /* COSMOPOLITAN_THIRD_PARTY_NSYNC_MU_SEMAPHORE_INTERNAL_H_ */
|
#endif /* COSMOPOLITAN_THIRD_PARTY_NSYNC_MU_SEMAPHORE_INTERNAL_H_ */
|
||||||
|
|
140
third_party/nsync/mu_semaphore_win32.c
vendored
Normal file
140
third_party/nsync/mu_semaphore_win32.c
vendored
Normal file
|
@ -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__ */
|
|
@ -23,7 +23,7 @@
|
||||||
#include "libc/stdio/stdio.h"
|
#include "libc/stdio/stdio.h"
|
||||||
#include "libc/sysv/consts/clock.h"
|
#include "libc/sysv/consts/clock.h"
|
||||||
|
|
||||||
#define MAXIMUM 1e8
|
#define MAXIMUM 1e9
|
||||||
#define ITERATIONS 10
|
#define ITERATIONS 10
|
||||||
|
|
||||||
void WarmUp(void) {
|
void WarmUp(void) {
|
||||||
|
@ -33,34 +33,36 @@ void WarmUp(void) {
|
||||||
|
|
||||||
void TestSleepRealRelative(void) {
|
void TestSleepRealRelative(void) {
|
||||||
printf("\n");
|
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) {
|
for (long nanos = 1; nanos < (long)MAXIMUM; nanos *= 2) {
|
||||||
struct timespec t1, t2, wf;
|
struct timespec t1, t2, wf;
|
||||||
wf = timespec_fromnanos(nanos);
|
wf = timespec_fromnanos(nanos);
|
||||||
clock_gettime(CLOCK_REALTIME_PRECISE, &t1);
|
clock_gettime(CLOCK_REALTIME, &t1);
|
||||||
for (int i = 0; i < ITERATIONS; ++i) {
|
for (int i = 0; i < ITERATIONS; ++i) {
|
||||||
npassert(!clock_nanosleep(CLOCK_REALTIME, 0, &wf, 0));
|
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;
|
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);
|
took - nanos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestSleepMonoRelative(void) {
|
void TestSleepMonoRelative(void) {
|
||||||
printf("\n");
|
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) {
|
for (long nanos = 1; nanos < (long)MAXIMUM; nanos *= 2) {
|
||||||
struct timespec t1, t2, wf;
|
struct timespec t1, t2, wf;
|
||||||
wf = timespec_fromnanos(nanos);
|
wf = timespec_fromnanos(nanos);
|
||||||
clock_gettime(CLOCK_REALTIME_PRECISE, &t1);
|
clock_gettime(CLOCK_REALTIME, &t1);
|
||||||
for (int i = 0; i < ITERATIONS; ++i) {
|
for (int i = 0; i < ITERATIONS; ++i) {
|
||||||
npassert(!clock_nanosleep(CLOCK_MONOTONIC, 0, &wf, 0));
|
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;
|
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);
|
took - nanos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue