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:
Justine Tunney 2023-10-09 11:56:21 -07:00
parent 820c3599ed
commit 3b4dbc9fdd
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
22 changed files with 870 additions and 330 deletions

View file

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

515
examples/greenbean2.c Normal file
View 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);
}

View file

@ -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, ...);

View file

@ -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()) {

View file

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

View file

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

View file

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

View file

@ -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) {

View file

@ -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

View file

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

View file

@ -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) */

View file

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

View file

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

View file

@ -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,

View file

@ -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;

View file

@ -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

View file

@ -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))

View file

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

View file

@ -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_ */

140
third_party/nsync/mu_semaphore_win32.c vendored Normal file
View 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__ */

View file

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