mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 06:53:33 +00:00
Make greenbean web server better
- Remove misguided __assert_disabled variable - Change EPROCLIM to be EAGAIN on BSD distros - Improve quality of greenbean with cancellations - Fix thread race condition crash with file descriptors
This commit is contained in:
parent
6cff3137c5
commit
0e087143fd
14 changed files with 247 additions and 149 deletions
|
@ -10,52 +10,34 @@
|
||||||
#include "libc/assert.h"
|
#include "libc/assert.h"
|
||||||
#include "libc/atomic.h"
|
#include "libc/atomic.h"
|
||||||
#include "libc/calls/calls.h"
|
#include "libc/calls/calls.h"
|
||||||
|
#include "libc/calls/pledge.h"
|
||||||
#include "libc/calls/struct/sigaction.h"
|
#include "libc/calls/struct/sigaction.h"
|
||||||
#include "libc/calls/struct/sigset.h"
|
|
||||||
#include "libc/calls/struct/timespec.h"
|
#include "libc/calls/struct/timespec.h"
|
||||||
#include "libc/calls/struct/timeval.h"
|
#include "libc/calls/struct/timeval.h"
|
||||||
#include "libc/dce.h"
|
#include "libc/dce.h"
|
||||||
#include "libc/errno.h"
|
#include "libc/errno.h"
|
||||||
#include "libc/fmt/conv.h"
|
#include "libc/fmt/conv.h"
|
||||||
#include "libc/fmt/itoa.h"
|
#include "libc/fmt/itoa.h"
|
||||||
#include "libc/intrin/atomic.h"
|
|
||||||
#include "libc/intrin/kprintf.h"
|
#include "libc/intrin/kprintf.h"
|
||||||
#include "libc/limits.h"
|
|
||||||
#include "libc/log/check.h"
|
|
||||||
#include "libc/log/log.h"
|
#include "libc/log/log.h"
|
||||||
#include "libc/macros.internal.h"
|
#include "libc/mem/gc.internal.h"
|
||||||
#include "libc/mem/mem.h"
|
#include "libc/mem/mem.h"
|
||||||
#include "libc/runtime/internal.h"
|
|
||||||
#include "libc/runtime/runtime.h"
|
#include "libc/runtime/runtime.h"
|
||||||
#include "libc/runtime/stack.h"
|
|
||||||
#include "libc/sock/sock.h"
|
#include "libc/sock/sock.h"
|
||||||
#include "libc/sock/struct/pollfd.h"
|
|
||||||
#include "libc/sock/struct/sockaddr.h"
|
#include "libc/sock/struct/sockaddr.h"
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
#include "libc/sysv/consts/af.h"
|
#include "libc/sysv/consts/af.h"
|
||||||
#include "libc/sysv/consts/clock.h"
|
|
||||||
#include "libc/sysv/consts/clone.h"
|
|
||||||
#include "libc/sysv/consts/ipproto.h"
|
|
||||||
#include "libc/sysv/consts/map.h"
|
|
||||||
#include "libc/sysv/consts/poll.h"
|
|
||||||
#include "libc/sysv/consts/prot.h"
|
|
||||||
#include "libc/sysv/consts/rlimit.h"
|
|
||||||
#include "libc/sysv/consts/sig.h"
|
#include "libc/sysv/consts/sig.h"
|
||||||
#include "libc/sysv/consts/so.h"
|
#include "libc/sysv/consts/so.h"
|
||||||
#include "libc/sysv/consts/sock.h"
|
#include "libc/sysv/consts/sock.h"
|
||||||
#include "libc/sysv/consts/sol.h"
|
#include "libc/sysv/consts/sol.h"
|
||||||
#include "libc/sysv/consts/tcp.h"
|
#include "libc/sysv/consts/tcp.h"
|
||||||
#include "libc/thread/spawn.h"
|
|
||||||
#include "libc/thread/thread.h"
|
#include "libc/thread/thread.h"
|
||||||
#include "libc/thread/tls.h"
|
#include "libc/thread/thread2.h"
|
||||||
#include "libc/thread/wait0.internal.h"
|
|
||||||
#include "libc/time/struct/tm.h"
|
|
||||||
#include "libc/time/time.h"
|
|
||||||
#include "net/http/http.h"
|
#include "net/http/http.h"
|
||||||
#include "net/http/url.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileoverview greenbean lightweighht threaded web server
|
* @fileoverview greenbean lightweight threaded web server
|
||||||
*
|
*
|
||||||
* $ make -j8 o//tool/net/greenbean.com
|
* $ make -j8 o//tool/net/greenbean.com
|
||||||
* $ o//tool/net/greenbean.com &
|
* $ o//tool/net/greenbean.com &
|
||||||
|
@ -95,9 +77,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define PORT 8080
|
#define PORT 8080
|
||||||
#define HEARTBEAT 100
|
#define KEEPALIVE 30000
|
||||||
#define KEEPALIVE 5000
|
#define LOGGING 1
|
||||||
#define LOGGING 0
|
|
||||||
|
|
||||||
#define STANDARD_RESPONSE_HEADERS \
|
#define STANDARD_RESPONSE_HEADERS \
|
||||||
"Server: greenbean/1.o\r\n" \
|
"Server: greenbean/1.o\r\n" \
|
||||||
|
@ -105,12 +86,25 @@
|
||||||
"Cache-Control: private; max-age=0\r\n"
|
"Cache-Control: private; max-age=0\r\n"
|
||||||
|
|
||||||
int threads;
|
int threads;
|
||||||
atomic_int workers;
|
int alwaysclose;
|
||||||
atomic_int messages;
|
atomic_int a_termsig;
|
||||||
atomic_int listening;
|
atomic_int a_workers;
|
||||||
atomic_int connections;
|
atomic_int a_messages;
|
||||||
atomic_int closingtime;
|
atomic_int a_listening;
|
||||||
const char *volatile status;
|
atomic_int a_connections;
|
||||||
|
pthread_cond_t statuscond;
|
||||||
|
pthread_mutex_t statuslock;
|
||||||
|
const char *volatile status = "";
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
void *Worker(void *id) {
|
void *Worker(void *id) {
|
||||||
int server, yes = 1;
|
int server, yes = 1;
|
||||||
|
@ -128,6 +122,8 @@ void *Worker(void *id) {
|
||||||
goto WorkerFinished;
|
goto WorkerFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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_RCVTIMEO, &timeo, sizeof(timeo));
|
||||||
setsockopt(server, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
|
setsockopt(server, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo));
|
||||||
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||||
|
@ -136,72 +132,84 @@ void *Worker(void *id) {
|
||||||
setsockopt(server, SOL_TCP, TCP_QUICKACK, &yes, sizeof(yes));
|
setsockopt(server, SOL_TCP, TCP_QUICKACK, &yes, sizeof(yes));
|
||||||
errno = 0;
|
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) {
|
if (bind(server, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
|
||||||
kprintf("%s() failed %m\n", "socket");
|
kprintf("%s() failed %m\n", "socket");
|
||||||
goto CloseWorker;
|
goto CloseWorker;
|
||||||
}
|
}
|
||||||
|
unassert(!listen(server, 1));
|
||||||
listen(server, 1);
|
|
||||||
|
|
||||||
// connection loop
|
// connection loop
|
||||||
++listening;
|
++a_listening;
|
||||||
while (!closingtime) {
|
SomethingImportantHappened();
|
||||||
struct tm tm;
|
while (!a_termsig) {
|
||||||
int64_t unixts;
|
|
||||||
ssize_t got, sent;
|
|
||||||
struct timespec ts;
|
|
||||||
struct HttpMessage msg;
|
|
||||||
uint32_t clientaddrsize;
|
uint32_t clientaddrsize;
|
||||||
struct sockaddr_in clientaddr;
|
struct sockaddr_in clientaddr;
|
||||||
char inbuf[1500], outbuf[512], *p, *q;
|
|
||||||
int client, inmsglen, outmsglen;
|
int client, inmsglen, outmsglen;
|
||||||
|
char inbuf[1500], outbuf[512], *p, *q;
|
||||||
|
|
||||||
// this slows the server down a lot but is needed on non-Linux to
|
// musl libc and cosmopolitan libc support a posix thread extension
|
||||||
// react to keyboard ctrl-c
|
// that makes thread cancellation work much better your io routines
|
||||||
if (!IsLinux() &&
|
// will just raise ECANCELED so you can check for cancellation with
|
||||||
poll(&(struct pollfd){server, POLLIN}, 1, HEARTBEAT) < 1) {
|
// normal logic rather than needing to push and pop cleanup handler
|
||||||
continue;
|
// functions onto the stack, or worse dealing with async interrupts
|
||||||
}
|
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0));
|
||||||
|
|
||||||
// wait for client connection
|
// wait for client connection
|
||||||
|
// we don't bother with poll() because this is actually very speedy
|
||||||
clientaddrsize = sizeof(clientaddr);
|
clientaddrsize = sizeof(clientaddr);
|
||||||
client = accept(server, (struct sockaddr *)&clientaddr, &clientaddrsize);
|
client = accept(server, (struct sockaddr *)&clientaddr, &clientaddrsize);
|
||||||
|
|
||||||
|
// turns cancellation off so we don't interrupt active http clients
|
||||||
|
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0));
|
||||||
|
|
||||||
// accept() can raise a very diverse number of errors but none of
|
// accept() can raise a very diverse number of errors but none of
|
||||||
// them are really true showstoppers that would necessitate us to
|
// them are really true showstoppers that would necessitate us to
|
||||||
// panic and abort the entire server, so we can just ignore these
|
// panic and abort the entire server, so we can just ignore these
|
||||||
if (client == -1) {
|
if (client == -1) {
|
||||||
// we used SO_RCVTIMEO and SO_SNDTIMEO because those settings are
|
// we used SO_RCVTIMEO and SO_SNDTIMEO because those settings are
|
||||||
// inherited by the accepted sockets, but using them also has the
|
// inherited by the accepted sockets, but using them also has the
|
||||||
// side-effect that the listening socket fails with EAGAIN, every
|
// side-effect that the listening socket fails with EAGAIN errors
|
||||||
// several seconds. we can use that to our advantage to check for
|
// which are harmless, and so are most other errors accept raises
|
||||||
// the ctrl-c shutdowne event; otherwise we retry the accept call
|
// e.g. ECANCELED, which lets us check closingtime without delay!
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
++connections;
|
++a_connections;
|
||||||
|
SomethingHappened();
|
||||||
|
|
||||||
// message loop
|
// message loop
|
||||||
|
ssize_t got, sent;
|
||||||
|
struct HttpMessage msg;
|
||||||
do {
|
do {
|
||||||
// parse the incoming http message
|
// parse the incoming http message
|
||||||
InitHttpMessage(&msg, kHttpRequest);
|
InitHttpMessage(&msg, kHttpRequest);
|
||||||
// we're not terrible concerned when errors happen here
|
// we're not terrible concerned when errors happen here
|
||||||
|
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0));
|
||||||
if ((got = read(client, inbuf, sizeof(inbuf))) <= 0) break;
|
if ((got = read(client, inbuf, sizeof(inbuf))) <= 0) break;
|
||||||
|
unassert(!pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, 0));
|
||||||
// check that client message wasn't fragmented into more reads
|
// check that client message wasn't fragmented into more reads
|
||||||
if ((inmsglen = ParseHttpMessage(&msg, inbuf, got)) <= 0) break;
|
if ((inmsglen = ParseHttpMessage(&msg, inbuf, got)) <= 0) break;
|
||||||
++messages;
|
++a_messages;
|
||||||
|
SomethingHappened();
|
||||||
|
|
||||||
#if LOGGING
|
#if LOGGING
|
||||||
// log the incoming http message
|
// log the incoming http message
|
||||||
clientip = ntohl(clientaddr.sin_addr.s_addr);
|
unsigned clientip = ntohl(clientaddr.sin_addr.s_addr);
|
||||||
kprintf("%6P get some %d.%d.%d.%d:%d %#.*s\n",
|
kprintf("\r\e[K%6P get some %d.%d.%d.%d:%d %#.*s\n",
|
||||||
(clientip & 0xff000000) >> 030, (clientip & 0x00ff0000) >> 020,
|
(clientip & 0xff000000) >> 030, (clientip & 0x00ff0000) >> 020,
|
||||||
(clientip & 0x0000ff00) >> 010, (clientip & 0x000000ff) >> 000,
|
(clientip & 0x0000ff00) >> 010, (clientip & 0x000000ff) >> 000,
|
||||||
ntohs(clientaddr.sin_port), msg.uri.b - msg.uri.a,
|
ntohs(clientaddr.sin_port), msg.uri.b - msg.uri.a,
|
||||||
inbuf + msg.uri.a);
|
inbuf + msg.uri.a);
|
||||||
|
SomethingHappened();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// display hello world html page for http://127.0.0.1:8080/
|
// 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 &&
|
if (msg.method == kHttpGet &&
|
||||||
(msg.uri.b - msg.uri.a == 1 && inbuf[msg.uri.a + 0] == '/')) {
|
(msg.uri.b - msg.uri.a == 1 && inbuf[msg.uri.a + 0] == '/')) {
|
||||||
q = "<!doctype html>\r\n"
|
q = "<!doctype html>\r\n"
|
||||||
|
@ -216,6 +224,9 @@ void *Worker(void *id) {
|
||||||
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
|
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
|
||||||
p = stpcpy(p, "\r\nContent-Length: ");
|
p = stpcpy(p, "\r\nContent-Length: ");
|
||||||
p = FormatInt32(p, strlen(q));
|
p = FormatInt32(p, strlen(q));
|
||||||
|
if (alwaysclose) {
|
||||||
|
p = stpcpy(p, "\r\nConnection: close");
|
||||||
|
}
|
||||||
p = stpcpy(p, "\r\n\r\n");
|
p = stpcpy(p, "\r\n\r\n");
|
||||||
p = stpcpy(p, q);
|
p = stpcpy(p, q);
|
||||||
outmsglen = p - outbuf;
|
outmsglen = p - outbuf;
|
||||||
|
@ -234,6 +245,9 @@ void *Worker(void *id) {
|
||||||
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
|
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
|
||||||
p = stpcpy(p, "\r\nContent-Length: ");
|
p = stpcpy(p, "\r\nContent-Length: ");
|
||||||
p = FormatInt32(p, strlen(q));
|
p = FormatInt32(p, strlen(q));
|
||||||
|
if (alwaysclose) {
|
||||||
|
p = stpcpy(p, "\r\nConnection: close");
|
||||||
|
}
|
||||||
p = stpcpy(p, "\r\n\r\n");
|
p = stpcpy(p, "\r\n\r\n");
|
||||||
p = stpcpy(p, q);
|
p = stpcpy(p, q);
|
||||||
outmsglen = p - outbuf;
|
outmsglen = p - outbuf;
|
||||||
|
@ -244,27 +258,27 @@ void *Worker(void *id) {
|
||||||
// amount, then since we sent the content length and checked
|
// amount, then since we sent the content length and checked
|
||||||
// that the client didn't attach a payload, we are so synced
|
// that the client didn't attach a payload, we are so synced
|
||||||
// thus we can safely process more messages
|
// thus we can safely process more messages
|
||||||
} while (got == inmsglen && sent == outmsglen &&
|
} while (!alwaysclose && //
|
||||||
|
got == inmsglen && //
|
||||||
|
sent == outmsglen && //
|
||||||
!msg.headers[kHttpContentLength].a &&
|
!msg.headers[kHttpContentLength].a &&
|
||||||
!msg.headers[kHttpTransferEncoding].a &&
|
!msg.headers[kHttpTransferEncoding].a &&
|
||||||
(msg.method == kHttpGet || msg.method == kHttpHead));
|
(msg.method == kHttpGet || msg.method == kHttpHead));
|
||||||
DestroyHttpMessage(&msg);
|
DestroyHttpMessage(&msg);
|
||||||
close(client);
|
close(client);
|
||||||
--connections;
|
--a_connections;
|
||||||
|
SomethingHappened();
|
||||||
}
|
}
|
||||||
--listening;
|
--a_listening;
|
||||||
|
|
||||||
// inform the parent that this clone has finished
|
// inform the parent that this clone has finished
|
||||||
CloseWorker:
|
CloseWorker:
|
||||||
close(server);
|
close(server);
|
||||||
WorkerFinished:
|
WorkerFinished:
|
||||||
--workers;
|
--a_workers;
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnCtrlC(int sig) {
|
SomethingImportantHappened();
|
||||||
closingtime = true;
|
return 0;
|
||||||
status = " shutting down...";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintStatus(void) {
|
void PrintStatus(void) {
|
||||||
|
@ -273,70 +287,170 @@ void PrintStatus(void) {
|
||||||
"listening=%d "
|
"listening=%d "
|
||||||
"connections=%d "
|
"connections=%d "
|
||||||
"messages=%d%s ",
|
"messages=%d%s ",
|
||||||
workers, listening, connections, messages, status);
|
a_workers, a_listening, a_connections, a_messages, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnTerm(int sig) {
|
||||||
|
a_termsig = sig;
|
||||||
|
status = " shutting down...";
|
||||||
|
SomethingHappened();
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
int i, rc;
|
int i;
|
||||||
pthread_t *th;
|
|
||||||
|
// print cpu registers and backtrace on crash
|
||||||
|
// note that pledge'll makes backtraces worse
|
||||||
|
// you can press ctrl+\ to trigger your crash
|
||||||
|
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;
|
uint32_t *hostips;
|
||||||
// ShowCrashReports();
|
for (hostips = gc(GetHostIps()), i = 0; hostips[i]; ++i) {
|
||||||
|
|
||||||
// listen for ctrl-c, hangup, and kill which shut down greenbean
|
|
||||||
status = "";
|
|
||||||
struct sigaction sa = {.sa_handler = OnCtrlC};
|
|
||||||
sigaction(SIGHUP, &sa, 0);
|
|
||||||
sigaction(SIGINT, &sa, 0);
|
|
||||||
sigaction(SIGTERM, &sa, 0);
|
|
||||||
|
|
||||||
// print all the ips that 0.0.0.0 will bind
|
|
||||||
for (hostips = GetHostIps(), i = 0; hostips[i]; ++i) {
|
|
||||||
kprintf("listening on http://%d.%d.%d.%d:%d\n",
|
kprintf("listening on http://%d.%d.%d.%d:%d\n",
|
||||||
(hostips[i] & 0xff000000) >> 030, (hostips[i] & 0x00ff0000) >> 020,
|
(hostips[i] & 0xff000000) >> 030, (hostips[i] & 0x00ff0000) >> 020,
|
||||||
(hostips[i] & 0x0000ff00) >> 010, (hostips[i] & 0x000000ff) >> 000,
|
(hostips[i] & 0x0000ff00) >> 010, (hostips[i] & 0x000000ff) >> 000,
|
||||||
PORT);
|
PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// you can pass the number of threads you want as the first command arg
|
||||||
threads = argc > 1 ? atoi(argv[1]) : __get_cpu_count();
|
threads = argc > 1 ? atoi(argv[1]) : __get_cpu_count();
|
||||||
if (!(1 <= threads && threads <= 100000)) {
|
if (!(1 <= threads && threads <= 100000)) {
|
||||||
kprintf("error: invalid number of threads: %d\n", threads);
|
kprintf("\r\e[Kerror: invalid number of threads: %d\n", threads);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// caveat emptor microsofties
|
||||||
|
if (IsWindows()) {
|
||||||
|
kprintf("sorry but windows isn't supported by the greenbean demo yet\n"
|
||||||
|
"because it doesn't support SO_REUSEPORT which is a nice for\n"
|
||||||
|
"gaining great performance on UNIX systems, with simple code\n"
|
||||||
|
"however windows will work fine if we limit it to one thread\n");
|
||||||
|
threads = 1; // we're going to make just one web server thread
|
||||||
|
alwaysclose = 1; // don't let client idle, since it'd block others
|
||||||
|
}
|
||||||
|
|
||||||
// secure the server
|
// 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
|
||||||
|
//
|
||||||
|
// we use an internal api to force threads to enable beforehand, since
|
||||||
|
// cosmopolitan code morphs the binary to support tls across platforms
|
||||||
|
// and doing that requires extra permissions we don't need for serving
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
__enable_threads();
|
__enable_threads();
|
||||||
|
__pledge_mode = PLEDGE_PENALTY_KILL_THREAD | PLEDGE_STDERR_LOGGING;
|
||||||
unveil("/dev/null", "rw");
|
unveil("/dev/null", "rw");
|
||||||
unveil(0, 0);
|
unveil(0, 0);
|
||||||
pledge("stdio inet", 0);
|
pledge("stdio inet", 0);
|
||||||
|
|
||||||
// spawn over 9,000 worker threads
|
// spawn over 9,000 worker threads
|
||||||
th = calloc(threads, sizeof(pthread_t));
|
//
|
||||||
|
// 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 like pthread attributes since they generally make thread spawns
|
||||||
|
// faster especially in cases where you need to make detached threads
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
unassert(!pthread_cond_init(&statuscond, 0));
|
||||||
|
unassert(!pthread_mutex_init(&statuslock, 0));
|
||||||
|
sigset_t block;
|
||||||
|
sigfillset(&block);
|
||||||
|
sigdelset(&block, SIGSEGV); // invalid memory access
|
||||||
|
sigdelset(&block, SIGBUS); // another kind of bad memory access
|
||||||
|
sigdelset(&block, SIGFPE); // divide by zero, etc.
|
||||||
|
sigdelset(&block, SIGSYS); // pledge violations
|
||||||
|
sigdelset(&block, SIGILL); // bad cpu opcode
|
||||||
|
pthread_attr_t attr;
|
||||||
|
unassert(!pthread_attr_init(&attr));
|
||||||
|
unassert(!pthread_attr_setsigmask_np(&attr, &block));
|
||||||
|
pthread_t *th = gc(calloc(threads, sizeof(pthread_t)));
|
||||||
for (i = 0; i < threads; ++i) {
|
for (i = 0; i < threads; ++i) {
|
||||||
++workers;
|
int rc;
|
||||||
if ((rc = pthread_create(th + i, 0, Worker, (void *)(intptr_t)i))) {
|
++a_workers;
|
||||||
--workers;
|
if ((rc = pthread_create(th + i, &attr, Worker, (void *)(intptr_t)i))) {
|
||||||
kprintf("error: pthread_create(%d) failed %s\n", i, strerror(rc));
|
--a_workers;
|
||||||
|
// rc will most likely be EAGAIN (we hit the process/thread limit)
|
||||||
|
kprintf("\r\e[Kerror: pthread_create(%d) failed: %s\n"
|
||||||
|
" try increasing RLIMIT_NPROC\n",
|
||||||
|
i, strerror(rc));
|
||||||
|
if (!i) exit(1);
|
||||||
|
threads = i;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!(i % 500)) {
|
if (!(i % 50)) {
|
||||||
PrintStatus();
|
PrintStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
unassert(!pthread_attr_destroy(&attr));
|
||||||
|
|
||||||
// wait for workers to terminate
|
// wait for workers to terminate
|
||||||
while (workers) {
|
unassert(!pthread_mutex_lock(&statuslock));
|
||||||
|
while (!a_termsig) {
|
||||||
PrintStatus();
|
PrintStatus();
|
||||||
usleep(HEARTBEAT * 1000);
|
unassert(!pthread_cond_wait(&statuscond, &statuslock));
|
||||||
|
usleep(20);
|
||||||
|
}
|
||||||
|
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!
|
||||||
|
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
|
||||||
|
for (i = 0; i < threads; ++i) {
|
||||||
|
unassert(!pthread_join(th[i], 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up terminal line
|
// clean up terminal line
|
||||||
kprintf("\r\e[K");
|
kprintf("\r\e[Kthank you for choosing \e[32mgreenbean\e[0m\n");
|
||||||
|
|
||||||
// join the workers
|
// clean up more resources
|
||||||
for (i = 0; i < threads; ++i) {
|
unassert(!pthread_mutex_destroy(&statuslock));
|
||||||
pthread_join(th[i], 0);
|
unassert(!pthread_cond_destroy(&statuscond));
|
||||||
|
|
||||||
|
// quality assurance
|
||||||
|
if (IsModeDbg()) {
|
||||||
|
CheckForMemoryLeaks();
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up memory
|
// propagate termination signal
|
||||||
free(hostips);
|
signal(a_termsig, SIG_DFL);
|
||||||
free(th);
|
raise(a_termsig);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#define _ASSERT_H
|
#define _ASSERT_H
|
||||||
COSMOPOLITAN_C_START_
|
COSMOPOLITAN_C_START_
|
||||||
|
|
||||||
void __assert_fail(const char *, const char *, int) relegated;
|
void __assert_fail(const char *, const char *, int) wontreturn relegated;
|
||||||
|
|
||||||
#ifdef NDEBUG
|
#ifdef NDEBUG
|
||||||
#define assert(x) ((void)0)
|
#define assert(x) ((void)0)
|
||||||
|
@ -28,7 +28,6 @@ void __assert_fail(const char *, const char *, int) relegated;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef _COSMO_SOURCE
|
#ifdef _COSMO_SOURCE
|
||||||
extern bool __assert_disable;
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#define unassert(x) __assert_macro(x, #x)
|
#define unassert(x) __assert_macro(x, #x)
|
||||||
#define npassert(x) __assert_macro(x, #x)
|
#define npassert(x) __assert_macro(x, #x)
|
||||||
|
@ -36,7 +35,6 @@ extern bool __assert_disable;
|
||||||
({ \
|
({ \
|
||||||
if (__builtin_expect(!(x), 0)) { \
|
if (__builtin_expect(!(x), 0)) { \
|
||||||
__assert_fail(s, __FILE__, __LINE__); \
|
__assert_fail(s, __FILE__, __LINE__); \
|
||||||
__builtin_trap(); \
|
|
||||||
} \
|
} \
|
||||||
(void)0; \
|
(void)0; \
|
||||||
})
|
})
|
||||||
|
|
|
@ -164,11 +164,22 @@ static bool __sig_deliver(int sigops, int sig, int si_code, ucontext_t *ctx) {
|
||||||
* Returns true if signal default action is to end process.
|
* Returns true if signal default action is to end process.
|
||||||
*/
|
*/
|
||||||
static textwindows bool __sig_is_fatal(int sig) {
|
static textwindows bool __sig_is_fatal(int sig) {
|
||||||
if (sig == SIGCHLD || sig == SIGURG || sig == SIGWINCH) {
|
return !(sig == SIGURG || //
|
||||||
return false;
|
sig == SIGCHLD || //
|
||||||
} else {
|
sig == SIGWINCH);
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Returns true if signal is so fatal it should dump core.
|
||||||
|
*/
|
||||||
|
static textwindows bool __sig_is_core(int sig) {
|
||||||
|
return sig == SIGSYS || //
|
||||||
|
sig == SIGBUS || //
|
||||||
|
sig == SIGSEGV || //
|
||||||
|
sig == SIGQUIT || //
|
||||||
|
sig == SIGTRAP || //
|
||||||
|
sig == SIGXCPU || //
|
||||||
|
sig == SIGXFSZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,11 +197,10 @@ textwindows bool __sig_handle(int sigops, int sig, int si_code,
|
||||||
char *end, sigbuf[21], output[22];
|
char *end, sigbuf[21], output[22];
|
||||||
signame = strsignal_r(sig, sigbuf);
|
signame = strsignal_r(sig, sigbuf);
|
||||||
STRACE("terminating due to uncaught %s", signame);
|
STRACE("terminating due to uncaught %s", signame);
|
||||||
hStderr = GetStdHandle(kNtStdErrorHandle);
|
if (__sig_is_core(sig)) {
|
||||||
end = stpcpy(stpcpy(output, signame), "\n");
|
hStderr = GetStdHandle(kNtStdErrorHandle);
|
||||||
WriteFile(hStderr, output, end - output, 0, 0);
|
end = stpcpy(stpcpy(output, signame), "\n");
|
||||||
if (_weaken(__restore_console_win32)) {
|
WriteFile(hStderr, output, end - output, 0, 0);
|
||||||
_weaken(__restore_console_win32)();
|
|
||||||
}
|
}
|
||||||
ExitProcess(sig);
|
ExitProcess(sig);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,7 @@
|
||||||
*/
|
*/
|
||||||
void __assert_fail(const char *expr, const char *file, int line) {
|
void __assert_fail(const char *expr, const char *file, int line) {
|
||||||
char ibuf[12];
|
char ibuf[12];
|
||||||
if (!__assert_disable) {
|
FormatInt32(ibuf, line);
|
||||||
FormatInt32(ibuf, line);
|
tinyprint(2, "\n", file, ":", ibuf, ": assert(", expr, ") failed\n", NULL);
|
||||||
tinyprint(2, file, ":", ibuf, ": assert(", expr, ") failed\n", NULL);
|
abort();
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,10 +68,6 @@ int raise(int sig) {
|
||||||
rc = sys_tkill(gettid(), sig, 0);
|
rc = sys_tkill(gettid(), sig, 0);
|
||||||
} else if (IsWindows() || IsMetal()) {
|
} else if (IsWindows() || IsMetal()) {
|
||||||
if (IsWindows() && sig == SIGKILL) {
|
if (IsWindows() && sig == SIGKILL) {
|
||||||
// TODO(jart): Isn't this implemented by __sig_raise()?
|
|
||||||
if (_weaken(__restore_console_win32)) {
|
|
||||||
_weaken(__restore_console_win32)();
|
|
||||||
}
|
|
||||||
ExitProcess(sig);
|
ExitProcess(sig);
|
||||||
} else {
|
} else {
|
||||||
rc = __sig_raise(sig, SI_TKILL);
|
rc = __sig_raise(sig, SI_TKILL);
|
||||||
|
|
|
@ -39,10 +39,12 @@ static volatile size_t mapsize;
|
||||||
* @asyncsignalsafe
|
* @asyncsignalsafe
|
||||||
*/
|
*/
|
||||||
int __ensurefds_unlocked(int fd) {
|
int __ensurefds_unlocked(int fd) {
|
||||||
|
size_t n;
|
||||||
if (fd < g_fds.n) return fd;
|
if (fd < g_fds.n) return fd;
|
||||||
g_fds.n = fd + 1;
|
n = fd + 1;
|
||||||
g_fds.e = _extend(g_fds.p, g_fds.n * sizeof(*g_fds.p), g_fds.e, MAP_PRIVATE,
|
g_fds.e = _extend(g_fds.p, n * sizeof(*g_fds.p), g_fds.e, MAP_PRIVATE,
|
||||||
kMemtrackFdsStart + kMemtrackFdsSize);
|
kMemtrackFdsStart + kMemtrackFdsSize);
|
||||||
|
g_fds.n = n;
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +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 2022 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. │
|
|
||||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables assert() failures at runtime.
|
|
||||||
*/
|
|
||||||
bool __assert_disable;
|
|
|
@ -155,7 +155,6 @@ void ShowCrashReports(void) {
|
||||||
InstallCrashHandler(SIGILL, __got_sigill, ef); // illegal instruction
|
InstallCrashHandler(SIGILL, __got_sigill, ef); // illegal instruction
|
||||||
InstallCrashHandler(SIGSEGV, __got_sigsegv, ef); // bad memory access
|
InstallCrashHandler(SIGSEGV, __got_sigsegv, ef); // bad memory access
|
||||||
InstallCrashHandler(SIGTRAP, __got_sigtrap, ef); // bad system call
|
InstallCrashHandler(SIGTRAP, __got_sigtrap, ef); // bad system call
|
||||||
InstallCrashHandler(SIGABRT, __got_sigabrt, ef); // abort() called
|
|
||||||
InstallCrashHandler(SIGBUS, __got_sigbus, ef); // misalign, mmap i/o failed
|
InstallCrashHandler(SIGBUS, __got_sigbus, ef); // misalign, mmap i/o failed
|
||||||
InstallCrashHandler(SIGURG, __got_sigurg, ef); // placeholder
|
InstallCrashHandler(SIGURG, __got_sigurg, ef); // placeholder
|
||||||
GetSymbolTable();
|
GetSymbolTable();
|
||||||
|
|
|
@ -690,6 +690,10 @@ errno_t clone(void *func, void *stk, size_t stksz, int flags, void *arg,
|
||||||
rc = ENOSYS;
|
rc = ENOSYS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SupportsBsd() && rc == EPROCLIM) {
|
||||||
|
rc = EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
STRACE("clone(%t, %p, %'zu, %#x, %p, %p, %p, %p) → %s", func, stk, stksz,
|
STRACE("clone(%t, %p, %'zu, %#x, %p, %p, %p, %p) → %s", func, stk, stksz,
|
||||||
flags, arg, ptid, tls, ctid, DescribeErrno(rc));
|
flags, arg, ptid, tls, ctid, DescribeErrno(rc));
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,7 @@ int ftrace_init(void);
|
||||||
void ftrace_hook(void);
|
void ftrace_hook(void);
|
||||||
void __morph_tls(void);
|
void __morph_tls(void);
|
||||||
void __enable_tls(void);
|
void __enable_tls(void);
|
||||||
void __enable_threads(void);
|
|
||||||
void *__cxa_finalize(void *);
|
void *__cxa_finalize(void *);
|
||||||
void __restore_console_win32(void);
|
|
||||||
void __stack_chk_fail(void) wontreturn relegated;
|
void __stack_chk_fail(void) wontreturn relegated;
|
||||||
void __stack_chk_fail_local(void) wontreturn relegated;
|
void __stack_chk_fail_local(void) wontreturn relegated;
|
||||||
void _jmpstack(void *, void *, ...) wontreturn;
|
void _jmpstack(void *, void *, ...) wontreturn;
|
||||||
|
|
|
@ -115,6 +115,7 @@ void __paginate(int, const char *);
|
||||||
void _weakfree(void *);
|
void _weakfree(void *);
|
||||||
void *_mapanon(size_t) attributeallocsize((1)) mallocesque;
|
void *_mapanon(size_t) attributeallocsize((1)) mallocesque;
|
||||||
void *_mapshared(size_t) attributeallocsize((1)) mallocesque;
|
void *_mapshared(size_t) attributeallocsize((1)) mallocesque;
|
||||||
|
void __enable_threads(void);
|
||||||
void __oom_hook(size_t);
|
void __oom_hook(size_t);
|
||||||
bool _isheap(void *);
|
bool _isheap(void *);
|
||||||
/* code morphing */
|
/* code morphing */
|
||||||
|
|
|
@ -22,11 +22,13 @@
|
||||||
#include "libc/thread/posixthread.internal.h"
|
#include "libc/thread/posixthread.internal.h"
|
||||||
#include "libc/thread/thread.h"
|
#include "libc/thread/thread.h"
|
||||||
#include "libc/thread/tls.h"
|
#include "libc/thread/tls.h"
|
||||||
|
#include "third_party/dlmalloc/dlmalloc.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases memory of detached threads that have terminated.
|
* Releases memory of detached threads that have terminated.
|
||||||
*/
|
*/
|
||||||
void pthread_decimate_np(void) {
|
void pthread_decimate_np(void) {
|
||||||
|
bool empty;
|
||||||
struct Dll *e;
|
struct Dll *e;
|
||||||
struct PosixThread *pt;
|
struct PosixThread *pt;
|
||||||
enum PosixThreadStatus status;
|
enum PosixThreadStatus status;
|
||||||
|
@ -44,5 +46,9 @@ StartOver:
|
||||||
goto StartOver;
|
goto StartOver;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
empty = dll_is_empty(_pthread_list);
|
||||||
pthread_spin_unlock(&_pthread_lock);
|
pthread_spin_unlock(&_pthread_lock);
|
||||||
|
if (empty) {
|
||||||
|
dlmalloc_trim(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ LIBC_THREAD_A_DIRECTDEPS = \
|
||||||
LIBC_STR \
|
LIBC_STR \
|
||||||
LIBC_SYSV \
|
LIBC_SYSV \
|
||||||
LIBC_SYSV_CALLS \
|
LIBC_SYSV_CALLS \
|
||||||
|
THIRD_PARTY_DLMALLOC \
|
||||||
THIRD_PARTY_NSYNC \
|
THIRD_PARTY_NSYNC \
|
||||||
THIRD_PARTY_NSYNC_MEM
|
THIRD_PARTY_NSYNC_MEM
|
||||||
|
|
||||||
|
|
|
@ -41,12 +41,6 @@ int Worker(void *arg, int tid) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(pthread_barrier_init, test0_isInvalid) {
|
|
||||||
__assert_disable = true;
|
|
||||||
ASSERT_EQ(EINVAL, pthread_barrier_init(&barrier, 0, 0));
|
|
||||||
__assert_disable = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(pthread_barrier_wait, test1) {
|
TEST(pthread_barrier_wait, test1) {
|
||||||
struct spawn t;
|
struct spawn t;
|
||||||
p = 0;
|
p = 0;
|
||||||
|
|
Loading…
Reference in a new issue