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:
Justine Tunney 2023-09-07 03:24:46 -07:00
parent 6cff3137c5
commit 0e087143fd
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
14 changed files with 247 additions and 149 deletions

View file

@ -10,52 +10,34 @@
#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/sigset.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/atomic.h"
#include "libc/intrin/kprintf.h"
#include "libc/limits.h"
#include "libc/log/check.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/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/sock/sock.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sock/struct/sockaddr.h"
#include "libc/str/str.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/so.h"
#include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h"
#include "libc/sysv/consts/tcp.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "libc/thread/wait0.internal.h"
#include "libc/time/struct/tm.h"
#include "libc/time/time.h"
#include "libc/thread/thread2.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
* $ o//tool/net/greenbean.com &
@ -95,9 +77,8 @@
*/
#define PORT 8080
#define HEARTBEAT 100
#define KEEPALIVE 5000
#define LOGGING 0
#define KEEPALIVE 30000
#define LOGGING 1
#define STANDARD_RESPONSE_HEADERS \
"Server: greenbean/1.o\r\n" \
@ -105,12 +86,25 @@
"Cache-Control: private; max-age=0\r\n"
int threads;
atomic_int workers;
atomic_int messages;
atomic_int listening;
atomic_int connections;
atomic_int closingtime;
const char *volatile status;
int alwaysclose;
atomic_int a_termsig;
atomic_int a_workers;
atomic_int a_messages;
atomic_int a_listening;
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) {
int server, yes = 1;
@ -128,6 +122,8 @@ void *Worker(void *id) {
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_SNDTIMEO, &timeo, sizeof(timeo));
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));
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("%s() failed %m\n", "socket");
goto CloseWorker;
}
listen(server, 1);
unassert(!listen(server, 1));
// connection loop
++listening;
while (!closingtime) {
struct tm tm;
int64_t unixts;
ssize_t got, sent;
struct timespec ts;
struct HttpMessage msg;
++a_listening;
SomethingImportantHappened();
while (!a_termsig) {
uint32_t clientaddrsize;
struct sockaddr_in clientaddr;
char inbuf[1500], outbuf[512], *p, *q;
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
// react to keyboard ctrl-c
if (!IsLinux() &&
poll(&(struct pollfd){server, POLLIN}, 1, HEARTBEAT) < 1) {
continue;
}
// musl libc and cosmopolitan libc support a posix thread extension
// that makes thread cancellation work much better your io routines
// will just raise ECANCELED so you can check for cancellation 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
// we don't bother with poll() because this is actually very speedy
clientaddrsize = sizeof(clientaddr);
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
// them are really true showstoppers that would necessitate us to
// panic and abort the entire server, so we can just ignore these
if (client == -1) {
// we used SO_RCVTIMEO and SO_SNDTIMEO because those settings are
// inherited by the accepted sockets, but using them also has the
// side-effect that the listening socket fails with EAGAIN, every
// several seconds. we can use that to our advantage to check for
// the ctrl-c shutdowne event; otherwise we retry the accept call
// side-effect that the listening socket fails with EAGAIN errors
// which are harmless, and so are most other errors accept raises
// e.g. ECANCELED, which lets us check closingtime without delay!
continue;
}
++connections;
++a_connections;
SomethingHappened();
// message loop
ssize_t got, sent;
struct HttpMessage msg;
do {
// parse the incoming http message
InitHttpMessage(&msg, kHttpRequest);
// 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;
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;
++messages;
++a_messages;
SomethingHappened();
#if LOGGING
// log the incoming http message
clientip = ntohl(clientaddr.sin_addr.s_addr);
kprintf("%6P get some %d.%d.%d.%d:%d %#.*s\n",
unsigned clientip = ntohl(clientaddr.sin_addr.s_addr);
kprintf("\r\e[K%6P get some %d.%d.%d.%d:%d %#.*s\n",
(clientip & 0xff000000) >> 030, (clientip & 0x00ff0000) >> 020,
(clientip & 0x0000ff00) >> 010, (clientip & 0x000000ff) >> 000,
ntohs(clientaddr.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"
@ -216,6 +224,9 @@ void *Worker(void *id) {
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
p = stpcpy(p, "\r\nContent-Length: ");
p = FormatInt32(p, strlen(q));
if (alwaysclose) {
p = stpcpy(p, "\r\nConnection: close");
}
p = stpcpy(p, "\r\n\r\n");
p = stpcpy(p, q);
outmsglen = p - outbuf;
@ -234,6 +245,9 @@ void *Worker(void *id) {
p = FormatHttpDateTime(p, gmtime_r(&unixts, &tm));
p = stpcpy(p, "\r\nContent-Length: ");
p = FormatInt32(p, strlen(q));
if (alwaysclose) {
p = stpcpy(p, "\r\nConnection: close");
}
p = stpcpy(p, "\r\n\r\n");
p = stpcpy(p, q);
outmsglen = p - outbuf;
@ -244,27 +258,27 @@ void *Worker(void *id) {
// 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 &&
} while (!alwaysclose && //
got == inmsglen && //
sent == outmsglen && //
!msg.headers[kHttpContentLength].a &&
!msg.headers[kHttpTransferEncoding].a &&
(msg.method == kHttpGet || msg.method == kHttpHead));
DestroyHttpMessage(&msg);
close(client);
--connections;
--a_connections;
SomethingHappened();
}
--listening;
--a_listening;
// inform the parent that this clone has finished
CloseWorker:
close(server);
WorkerFinished:
--workers;
return 0;
}
--a_workers;
void OnCtrlC(int sig) {
closingtime = true;
status = " shutting down...";
SomethingImportantHappened();
return 0;
}
void PrintStatus(void) {
@ -273,70 +287,170 @@ void PrintStatus(void) {
"listening=%d "
"connections=%d "
"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 i, rc;
pthread_t *th;
int i;
// 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;
// ShowCrashReports();
// 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) {
for (hostips = gc(GetHostIps()), i = 0; hostips[i]; ++i) {
kprintf("listening on http://%d.%d.%d.%d:%d\n",
(hostips[i] & 0xff000000) >> 030, (hostips[i] & 0x00ff0000) >> 020,
(hostips[i] & 0x0000ff00) >> 010, (hostips[i] & 0x000000ff) >> 000,
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("error: invalid number of threads: %d\n", threads);
kprintf("\r\e[Kerror: invalid number of threads: %d\n", threads);
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
//
// 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();
__pledge_mode = PLEDGE_PENALTY_KILL_THREAD | PLEDGE_STDERR_LOGGING;
unveil("/dev/null", "rw");
unveil(0, 0);
pledge("stdio inet", 0);
// 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) {
++workers;
if ((rc = pthread_create(th + i, 0, Worker, (void *)(intptr_t)i))) {
--workers;
kprintf("error: pthread_create(%d) failed %s\n", i, strerror(rc));
int rc;
++a_workers;
if ((rc = pthread_create(th + i, &attr, Worker, (void *)(intptr_t)i))) {
--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();
}
}
unassert(!pthread_attr_destroy(&attr));
// wait for workers to terminate
while (workers) {
unassert(!pthread_mutex_lock(&statuslock));
while (!a_termsig) {
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
kprintf("\r\e[K");
kprintf("\r\e[Kthank you for choosing \e[32mgreenbean\e[0m\n");
// join the workers
for (i = 0; i < threads; ++i) {
pthread_join(th[i], 0);
// clean up more resources
unassert(!pthread_mutex_destroy(&statuslock));
unassert(!pthread_cond_destroy(&statuscond));
// quality assurance
if (IsModeDbg()) {
CheckForMemoryLeaks();
}
// clean up memory
free(hostips);
free(th);
// propagate termination signal
signal(a_termsig, SIG_DFL);
raise(a_termsig);
}

View file

@ -14,7 +14,7 @@
#define _ASSERT_H
COSMOPOLITAN_C_START_
void __assert_fail(const char *, const char *, int) relegated;
void __assert_fail(const char *, const char *, int) wontreturn relegated;
#ifdef NDEBUG
#define assert(x) ((void)0)
@ -28,7 +28,6 @@ void __assert_fail(const char *, const char *, int) relegated;
#endif
#ifdef _COSMO_SOURCE
extern bool __assert_disable;
#ifndef NDEBUG
#define unassert(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)) { \
__assert_fail(s, __FILE__, __LINE__); \
__builtin_trap(); \
} \
(void)0; \
})

View file

@ -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.
*/
static textwindows bool __sig_is_fatal(int sig) {
if (sig == SIGCHLD || sig == SIGURG || sig == SIGWINCH) {
return false;
} else {
return true;
}
return !(sig == SIGURG || //
sig == SIGCHLD || //
sig == SIGWINCH);
}
/**
* 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];
signame = strsignal_r(sig, sigbuf);
STRACE("terminating due to uncaught %s", signame);
hStderr = GetStdHandle(kNtStdErrorHandle);
end = stpcpy(stpcpy(output, signame), "\n");
WriteFile(hStderr, output, end - output, 0, 0);
if (_weaken(__restore_console_win32)) {
_weaken(__restore_console_win32)();
if (__sig_is_core(sig)) {
hStderr = GetStdHandle(kNtStdErrorHandle);
end = stpcpy(stpcpy(output, signame), "\n");
WriteFile(hStderr, output, end - output, 0, 0);
}
ExitProcess(sig);
}

View file

@ -26,9 +26,7 @@
*/
void __assert_fail(const char *expr, const char *file, int line) {
char ibuf[12];
if (!__assert_disable) {
FormatInt32(ibuf, line);
tinyprint(2, file, ":", ibuf, ": assert(", expr, ") failed\n", NULL);
abort();
}
FormatInt32(ibuf, line);
tinyprint(2, "\n", file, ":", ibuf, ": assert(", expr, ") failed\n", NULL);
abort();
}

View file

@ -68,10 +68,6 @@ int raise(int sig) {
rc = sys_tkill(gettid(), sig, 0);
} else if (IsWindows() || IsMetal()) {
if (IsWindows() && sig == SIGKILL) {
// TODO(jart): Isn't this implemented by __sig_raise()?
if (_weaken(__restore_console_win32)) {
_weaken(__restore_console_win32)();
}
ExitProcess(sig);
} else {
rc = __sig_raise(sig, SI_TKILL);

View file

@ -39,10 +39,12 @@ static volatile size_t mapsize;
* @asyncsignalsafe
*/
int __ensurefds_unlocked(int fd) {
size_t n;
if (fd < g_fds.n) return fd;
g_fds.n = fd + 1;
g_fds.e = _extend(g_fds.p, g_fds.n * sizeof(*g_fds.p), g_fds.e, MAP_PRIVATE,
n = fd + 1;
g_fds.e = _extend(g_fds.p, n * sizeof(*g_fds.p), g_fds.e, MAP_PRIVATE,
kMemtrackFdsStart + kMemtrackFdsSize);
g_fds.n = n;
return fd;
}

View file

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

View file

@ -155,7 +155,6 @@ void ShowCrashReports(void) {
InstallCrashHandler(SIGILL, __got_sigill, ef); // illegal instruction
InstallCrashHandler(SIGSEGV, __got_sigsegv, ef); // bad memory access
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(SIGURG, __got_sigurg, ef); // placeholder
GetSymbolTable();

View file

@ -690,6 +690,10 @@ errno_t clone(void *func, void *stk, size_t stksz, int flags, void *arg,
rc = ENOSYS;
}
if (SupportsBsd() && rc == EPROCLIM) {
rc = EAGAIN;
}
STRACE("clone(%t, %p, %'zu, %#x, %p, %p, %p, %p) → %s", func, stk, stksz,
flags, arg, ptid, tls, ctid, DescribeErrno(rc));

View file

@ -35,9 +35,7 @@ int ftrace_init(void);
void ftrace_hook(void);
void __morph_tls(void);
void __enable_tls(void);
void __enable_threads(void);
void *__cxa_finalize(void *);
void __restore_console_win32(void);
void __stack_chk_fail(void) wontreturn relegated;
void __stack_chk_fail_local(void) wontreturn relegated;
void _jmpstack(void *, void *, ...) wontreturn;

View file

@ -115,6 +115,7 @@ void __paginate(int, const char *);
void _weakfree(void *);
void *_mapanon(size_t) attributeallocsize((1)) mallocesque;
void *_mapshared(size_t) attributeallocsize((1)) mallocesque;
void __enable_threads(void);
void __oom_hook(size_t);
bool _isheap(void *);
/* code morphing */

View file

@ -22,11 +22,13 @@
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "third_party/dlmalloc/dlmalloc.h"
/**
* Releases memory of detached threads that have terminated.
*/
void pthread_decimate_np(void) {
bool empty;
struct Dll *e;
struct PosixThread *pt;
enum PosixThreadStatus status;
@ -44,5 +46,9 @@ StartOver:
goto StartOver;
}
}
empty = dll_is_empty(_pthread_list);
pthread_spin_unlock(&_pthread_lock);
if (empty) {
dlmalloc_trim(0);
}
}

View file

@ -34,6 +34,7 @@ LIBC_THREAD_A_DIRECTDEPS = \
LIBC_STR \
LIBC_SYSV \
LIBC_SYSV_CALLS \
THIRD_PARTY_DLMALLOC \
THIRD_PARTY_NSYNC \
THIRD_PARTY_NSYNC_MEM

View file

@ -41,12 +41,6 @@ int Worker(void *arg, int tid) {
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) {
struct spawn t;
p = 0;