Improve multithreading

This commit is contained in:
Justine Tunney 2024-07-21 06:41:30 -07:00
parent d3167126aa
commit 30afd6ddbb
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
38 changed files with 752 additions and 174 deletions

View file

@ -396,9 +396,7 @@ static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) { textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) {
int rc; int rc;
BLOCK_SIGNALS; BLOCK_SIGNALS;
_pthread_ref(pt);
rc = __sig_killer(pt, sig, sic); rc = __sig_killer(pt, sig, sic);
_pthread_unref(pt);
ALLOW_SIGNALS; ALLOW_SIGNALS;
return rc; return rc;
} }

View file

@ -146,7 +146,7 @@ int sigaltstack(const struct sigaltstack *neu, struct sigaltstack *old) {
} else { } else {
rc = sigaltstack_cosmo(neu, old); rc = sigaltstack_cosmo(neu, old);
} }
STRACE("sigaltstack(%s, [%s]) → %d% m", DescribeSigaltstk(0, neu), STRACE("sigaltstack(%s, [%s]) → %d% m", DescribeSigaltstack(0, neu),
DescribeSigaltstk(0, old), rc); DescribeSigaltstack(0, old), rc);
return rc; return rc;
} }

View file

@ -4,8 +4,8 @@
#include "libc/mem/alloca.h" #include "libc/mem/alloca.h"
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
const char *DescribeSigaltstk(char[128], int, const struct sigaltstack *); const char *DescribeSigaltstack(char[128], int, const struct sigaltstack *);
#define DescribeSigaltstk(rc, ss) DescribeSigaltstk(alloca(128), rc, ss) #define DescribeSigaltstack(rc, ss) DescribeSigaltstack(alloca(128), rc, ss)
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGALTSTACK_INTERNAL_H_ */ #endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_SIGALTSTACK_INTERNAL_H_ */

View file

@ -27,6 +27,7 @@ const char *DescribeItimer(char[12], int) libcesque;
const char *DescribeMapFlags(char[64], int) libcesque; const char *DescribeMapFlags(char[64], int) libcesque;
const char *DescribeMapping(char[8], int, int) libcesque; const char *DescribeMapping(char[8], int, int) libcesque;
const char *DescribeMremapFlags(char[30], int) libcesque; const char *DescribeMremapFlags(char[30], int) libcesque;
const char *DescribeMsyncFlags(char[48], int) libcesque;
const char *DescribeNtConsoleInFlags(char[256], uint32_t) libcesque; const char *DescribeNtConsoleInFlags(char[256], uint32_t) libcesque;
const char *DescribeNtConsoleOutFlags(char[128], uint32_t) libcesque; const char *DescribeNtConsoleOutFlags(char[128], uint32_t) libcesque;
const char *DescribeNtCreationDisposition(uint32_t) libcesque; const char *DescribeNtCreationDisposition(uint32_t) libcesque;
@ -54,6 +55,7 @@ const char *DescribeRlimitName(char[20], int) libcesque;
const char *DescribeSchedPolicy(char[48], int) libcesque; const char *DescribeSchedPolicy(char[48], int) libcesque;
const char *DescribeSeccompOperation(int) libcesque; const char *DescribeSeccompOperation(int) libcesque;
const char *DescribeSiCode(char[20], int, int) libcesque; const char *DescribeSiCode(char[20], int, int) libcesque;
const char *DescribeSigaltstackFlags(char[22], int) libcesque;
const char *DescribeSleepFlags(char[16], int) libcesque; const char *DescribeSleepFlags(char[16], int) libcesque;
const char *DescribeSockLevel(char[12], int) libcesque; const char *DescribeSockLevel(char[12], int) libcesque;
const char *DescribeSockOptname(char[32], int, int) libcesque; const char *DescribeSockOptname(char[32], int, int) libcesque;
@ -82,6 +84,7 @@ const char *DescribeWhichPrio(char[12], int) libcesque;
#define DescribeMapFlags(x) DescribeMapFlags(alloca(64), x) #define DescribeMapFlags(x) DescribeMapFlags(alloca(64), x)
#define DescribeMapping(x, y) DescribeMapping(alloca(8), x, y) #define DescribeMapping(x, y) DescribeMapping(alloca(8), x, y)
#define DescribeMremapFlags(x) DescribeMremapFlags(alloca(30), x) #define DescribeMremapFlags(x) DescribeMremapFlags(alloca(30), x)
#define DescribeMsyncFlags(x) DescribeMsyncFlags(alloca(48), x)
#define DescribeNtConsoleInFlags(x) DescribeNtConsoleInFlags(alloca(256), x) #define DescribeNtConsoleInFlags(x) DescribeNtConsoleInFlags(alloca(256), x)
#define DescribeNtConsoleOutFlags(x) DescribeNtConsoleOutFlags(alloca(128), x) #define DescribeNtConsoleOutFlags(x) DescribeNtConsoleOutFlags(alloca(128), x)
#define DescribeNtFileAccessFlags(x) DescribeNtFileAccessFlags(alloca(512), x) #define DescribeNtFileAccessFlags(x) DescribeNtFileAccessFlags(alloca(512), x)
@ -107,6 +110,7 @@ const char *DescribeWhichPrio(char[12], int) libcesque;
#define DescribeRlimitName(rl) DescribeRlimitName(alloca(20), rl) #define DescribeRlimitName(rl) DescribeRlimitName(alloca(20), rl)
#define DescribeSchedPolicy(x) DescribeSchedPolicy(alloca(48), x) #define DescribeSchedPolicy(x) DescribeSchedPolicy(alloca(48), x)
#define DescribeSiCode(x, y) DescribeSiCode(alloca(20), x, y) #define DescribeSiCode(x, y) DescribeSiCode(alloca(20), x, y)
#define DescribeSigaltstackFlags(x) DescribeSigaltstackFlags(alloca(22), x)
#define DescribeSleepFlags(x) DescribeSleepFlags(alloca(16), x) #define DescribeSleepFlags(x) DescribeSleepFlags(alloca(16), x)
#define DescribeSockLevel(x) DescribeSockLevel(alloca(12), x) #define DescribeSockLevel(x) DescribeSockLevel(alloca(12), x)
#define DescribeSockOptname(x, y) DescribeSockOptname(alloca(32), x, y) #define DescribeSockOptname(x, y) DescribeSockOptname(alloca(32), x, y)

View file

@ -1,7 +1,7 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ /*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 Justine Alexandra Roberts Tunney Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the any purpose with or without fee is hereby granted, provided that the
@ -16,20 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h" #include "libc/intrin/describeflags.h"
#include "libc/intrin/atomic.h" #include "libc/macros.internal.h"
#include "libc/intrin/weaken.h" #include "libc/sysv/consts/msync.h"
#include "libc/thread/posixthread.internal.h"
static bool _pthread_deref(struct PosixThread *pt) { const char *(DescribeMsyncFlags)(char buf[48], int x) {
int refs = atomic_load_explicit(&pt->pt_refs, memory_order_acquire); const struct DescribeFlags kMsyncFlags[] = {
return !refs || !atomic_fetch_sub(&pt->pt_refs, 1); {MS_SYNC, "SYNC"}, //
} {MS_ASYNC, "ASYNC"}, //
{MS_INVALIDATE, "INVALIDATE"}, //
void _pthread_unref(struct PosixThread *pt) { };
if (_pthread_deref(pt)) { return DescribeFlags(buf, 48, kMsyncFlags, ARRAYLEN(kMsyncFlags), "MS_", x);
unassert(_weaken(_pthread_free));
_weaken(_pthread_free)(pt, false);
_weaken(_pthread_decimate)();
}
} }

View file

@ -21,8 +21,8 @@
#include "libc/intrin/describeflags.h" #include "libc/intrin/describeflags.h"
#include "libc/intrin/kprintf.h" #include "libc/intrin/kprintf.h"
const char *(DescribeSigaltstk)(char buf[128], int rc, const char *(DescribeSigaltstack)(char buf[128], int rc,
const struct sigaltstack *ss) { const struct sigaltstack *ss) {
if (rc == -1) if (rc == -1)
return "n/a"; return "n/a";
if (!ss) if (!ss)
@ -30,8 +30,8 @@ const char *(DescribeSigaltstk)(char buf[128], int rc,
if (kisdangerous(ss)) { if (kisdangerous(ss)) {
ksnprintf(buf, 128, "%p", ss); ksnprintf(buf, 128, "%p", ss);
} else { } else {
ksnprintf(buf, 128, "{.ss_sp=%p, .ss_flags=%#lx, .ss_size=%'zu}", ss->ss_sp, ksnprintf(buf, 128, "{.ss_sp=%p, .ss_flags=%s, .ss_size=%'zu}", ss->ss_sp,
ss->ss_flags, ss->ss_size); DescribeSigaltstackFlags(ss->ss_flags), ss->ss_size);
} }
return buf; return buf;
} }

View file

@ -0,0 +1,30 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et 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.
*/
#include "libc/intrin/describeflags.h"
#include "libc/macros.internal.h"
#include "libc/sysv/consts/ss.h"
const char *(DescribeSigaltstackFlags)(char buf[22], int x) {
const struct DescribeFlags kSigaltstackFlags[] = {
{SS_ONSTACK, "ONSTACK"}, //
{SS_DISABLE, "DISABLE"}, //
};
return DescribeFlags(buf, 48, kSigaltstackFlags, ARRAYLEN(kSigaltstackFlags),
"SS_", x);
}

View file

@ -91,12 +91,21 @@ privileged bool __maps_lock(void) {
tib = __get_tls_privileged(); tib = __get_tls_privileged();
if (atomic_fetch_add_explicit(&tib->tib_relock_maps, 1, memory_order_relaxed)) if (atomic_fetch_add_explicit(&tib->tib_relock_maps, 1, memory_order_relaxed))
return true; return true;
int backoff = 0;
while (atomic_exchange_explicit(&__maps.lock, 1, memory_order_acquire)) { while (atomic_exchange_explicit(&__maps.lock, 1, memory_order_acquire)) {
if (backoff < 7) {
volatile int i;
for (i = 0; i != 1 << backoff; i++) {
}
backoff++;
} else {
// STRACE("pthread_delay_np(__maps)");
#if defined(__GNUC__) && defined(__aarch64__) #if defined(__GNUC__) && defined(__aarch64__)
__asm__ volatile("yield"); __asm__ volatile("yield");
#elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) #elif defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
__asm__ volatile("pause"); __asm__ volatile("pause");
#endif #endif
}
} }
return false; return false;
} }

View file

@ -834,7 +834,8 @@ void *mremap(void *old_addr, size_t old_size, size_t new_size, int flags, ...) {
*/ */
int munmap(void *addr, size_t size) { int munmap(void *addr, size_t size) {
int rc = __munmap(addr, size); int rc = __munmap(addr, size);
STRACE("munmap(%p, %'zu) → %d% m", addr, size, rc); STRACE("munmap(%p, %'zu) → %d% m (%'zu bytes total)", addr, size, rc,
__maps.pages * __pagesize);
return rc; return rc;
} }

View file

@ -23,6 +23,7 @@
#include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/describeflags.h"
#include "libc/intrin/strace.h" #include "libc/intrin/strace.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
@ -93,6 +94,7 @@ int msync(void *addr, size_t size, int flags) {
END_CANCELATION_POINT; END_CANCELATION_POINT;
Finished: Finished:
STRACE("msync(%p, %'zu, %#x) → %d% m", addr, size, flags, rc); STRACE("msync(%p, %'zu, %s) → %d% m", addr, size, DescribeMsyncFlags(flags),
rc);
return rc; return rc;
} }

View file

@ -76,7 +76,7 @@ void CheckForMemoryLeaks(void) {
// validate usage of this api // validate usage of this api
if (_weaken(_pthread_decimate)) if (_weaken(_pthread_decimate))
_weaken(_pthread_decimate)(); _weaken(_pthread_decimate)(false);
if (!pthread_orphan_np()) if (!pthread_orphan_np())
kprintf("warning: called CheckForMemoryLeaks() from non-orphaned thread\n"); kprintf("warning: called CheckForMemoryLeaks() from non-orphaned thread\n");

View file

@ -79,11 +79,22 @@ void __cxa_thread_finalize(void) {
struct Dtor *dtor; struct Dtor *dtor;
struct CosmoTib *tib; struct CosmoTib *tib;
tib = __get_tls(); tib = __get_tls();
// "Any cancellation cleanup handlers that have been pushed and not
// yet popped shall be popped in the reverse order that they were
// pushed and then executed." ──Quoth POSIX.1-2017
_pthread_unwind(tib); _pthread_unwind(tib);
// "After all cancellation cleanup handlers have been executed, if the
// thread has any thread-specific data, appropriate destructor
// functions shall be called in an unspecified order."
// ──Quoth POSIX.1-2017
if (tib->tib_nsync) if (tib->tib_nsync)
_weaken(nsync_waiter_destroy)(tib->tib_nsync); _weaken(nsync_waiter_destroy)(tib->tib_nsync);
_pthread_unkey(tib); _pthread_unkey(tib);
_pthread_ungarbage(tib); _pthread_ungarbage(tib);
while ((dtor = tib->tib_atexit)) { while ((dtor = tib->tib_atexit)) {
STRACE("__cxa_finalize(%t, %p)", dtor->fun, dtor->arg); STRACE("__cxa_finalize(%t, %p)", dtor->fun, dtor->arg);
tib->tib_atexit = dtor->next; tib->tib_atexit = dtor->next;

View file

@ -151,23 +151,20 @@ int main(int argc, char *argv[]) {
a->teardown(0); a->teardown(0);
} }
} }
if (_weaken(TearDownOnce)) { if (_weaken(TearDownOnce))
_weaken(TearDownOnce)(); _weaken(TearDownOnce)();
}
// make sure threads are in a good state // make sure threads are in a good state
if (_weaken(_pthread_decimate)) { if (_weaken(_pthread_decimate))
_weaken(_pthread_decimate)(); _weaken(_pthread_decimate)(false);
}
if (_weaken(pthread_orphan_np) && !_weaken(pthread_orphan_np)()) { if (_weaken(pthread_orphan_np) && !_weaken(pthread_orphan_np)()) {
tinyprint(2, "error: tests ended with threads still active\n", NULL); tinyprint(2, "error: tests ended with threads still active\n", NULL);
_Exit(1); _Exit(1);
} }
// check for memory leaks // check for memory leaks
if (!g_testlib_failed) { if (!g_testlib_failed)
CheckForMemoryLeaks(); CheckForMemoryLeaks();
}
// we're done! // we're done!
int status = MIN(255, g_testlib_failed); int status = MIN(255, g_testlib_failed);

View file

@ -74,7 +74,7 @@ struct PosixThread {
atomic_int pt_canceled; // 0x04: thread has bad beliefs atomic_int pt_canceled; // 0x04: thread has bad beliefs
_Atomic(enum PosixThreadStatus) pt_status; _Atomic(enum PosixThreadStatus) pt_status;
atomic_int ptid; // transitions 0 → tid atomic_int ptid; // transitions 0 → tid
atomic_int pt_refs; // negative means free atomic_int pt_refs; // prevents decimation
void *(*pt_start)(void *); // creation callback void *(*pt_start)(void *); // creation callback
void *pt_arg; // start's parameter void *pt_arg; // start's parameter
void *pt_rc; // start's return value void *pt_rc; // start's return value
@ -103,14 +103,13 @@ int _pthread_setschedparam_freebsd(int, int, const struct sched_param *);
int _pthread_tid(struct PosixThread *) libcesque; int _pthread_tid(struct PosixThread *) libcesque;
intptr_t _pthread_syshand(struct PosixThread *) libcesque; intptr_t _pthread_syshand(struct PosixThread *) libcesque;
long _pthread_cancel_ack(void) libcesque; long _pthread_cancel_ack(void) libcesque;
void _pthread_decimate(void) libcesque; void _pthread_decimate(bool) libcesque;
void _pthread_free(struct PosixThread *, bool) libcesque; void _pthread_free(struct PosixThread *) libcesque;
void _pthread_lock(void) libcesque; void _pthread_lock(void) libcesque;
void _pthread_onfork_child(void) libcesque; void _pthread_onfork_child(void) libcesque;
void _pthread_onfork_parent(void) libcesque; void _pthread_onfork_parent(void) libcesque;
void _pthread_onfork_prepare(void) libcesque; void _pthread_onfork_prepare(void) libcesque;
void _pthread_unlock(void) libcesque; void _pthread_unlock(void) libcesque;
void _pthread_unref(struct PosixThread *) libcesque;
void _pthread_zombify(struct PosixThread *) libcesque; void _pthread_zombify(struct PosixThread *) libcesque;
forceinline pureconst struct PosixThread *_pthread_self(void) { forceinline pureconst struct PosixThread *_pthread_self(void) {
@ -118,7 +117,11 @@ forceinline pureconst struct PosixThread *_pthread_self(void) {
} }
forceinline void _pthread_ref(struct PosixThread *pt) { forceinline void _pthread_ref(struct PosixThread *pt) {
atomic_fetch_add_explicit(&pt->pt_refs, 1, memory_order_relaxed); atomic_fetch_add_explicit(&pt->pt_refs, 1, memory_order_acq_rel);
}
forceinline void _pthread_unref(struct PosixThread *pt) {
atomic_fetch_sub_explicit(&pt->pt_refs, 1, memory_order_acq_rel);
} }
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_

View file

@ -8,5 +8,7 @@
#define PT_MASKED 16 #define PT_MASKED 16
#define PT_INCANCEL 32 #define PT_INCANCEL 32
#define PT_OPENBSD_KLUDGE 64 #define PT_OPENBSD_KLUDGE 64
#define PT_EXITING 128
#define PT_OWNSIGALTSTACK 256
#endif /* COSMOPOLITAN_LIBC_THREAD_PT_H_ */ #endif /* COSMOPOLITAN_LIBC_THREAD_PT_H_ */

View file

@ -19,7 +19,7 @@
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
/** /**
* Returns size of unmapped pages at bottom of stack. * Returns size of protected region at bottom of thread stack.
* *
* @param guardsize will be set to guard size in bytes * @param guardsize will be set to guard size in bytes
* @return 0 on success, or errno on error * @return 0 on success, or errno on error

View file

@ -0,0 +1,34 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et 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.
*/
#include "libc/runtime/stack.h"
#include "libc/thread/thread.h"
/**
* Returns configuration for thread signal stack.
*
* @param stackaddr will be set to signal stack address
* @return 0 on success, or errno on error
* @see pthread_attr_setsigaltstacksize_np()
*/
errno_t pthread_attr_getsigaltstack_np(const pthread_attr_t *attr,
void **stackaddr, size_t *stacksize) {
*stackaddr = attr->__sigaltstackaddr;
*stacksize = attr->__sigaltstacksize;
return 0;
}

View file

@ -0,0 +1,36 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et 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.
*/
#include "libc/runtime/stack.h"
#include "libc/thread/thread.h"
/**
* Returns size of thread signal stack.
*
* This defaults to zero, which means that cosmo won't allocate a
* managed signal stack for newly created threads.
*
* @param x will be set to stack size in bytes
* @return 0 on success, or errno on error
* @see pthread_attr_setsigaltstacksize_np()
*/
errno_t pthread_attr_getsigaltstacksize_np(const pthread_attr_t *a,
size_t *stacksize) {
*stacksize = a->__sigaltstacksize;
return 0;
}

View file

@ -19,18 +19,16 @@
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
/** /**
* Sets size of unmapped pages at bottom of stack. * Sets size of protected region at bottom of thread stack.
* *
* This value will be rounded up to the host microprocessor page size, * Cosmopolitan sets this value to `sysconf(_SC_PAGESIZE)` by default.
* which is usually 4096 or 16384. It's important to write code in such
* a way that that code can't skip over the guard area. GCC has warnings
* like `-Wframe-larger-than=4096 -Walloca-larger-than=4096` which help
* guarantee your code is safe in this regard. It should be assumed the
* guard pages exist beneath the stack pointer, rather than the bottom
* of the stack, since guard pages are also used to grow down commit,
* which can be poked using CheckLargeStackAllocation().
* *
* @param guardsize contains guard size in bytes * You may set `guardsize` to disable the stack guard feature and gain a
* slight performance advantage by avoiding mprotect() calls. Note that
* it could make your code more prone to silent unreported corruption.
*
* @param guardsize contains guard size in bytes, which is implicitly
* rounded up to `sysconf(_SC_PAGESIZE)`, or zero to disable
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
*/ */
errno_t pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize) { errno_t pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize) {

View file

@ -0,0 +1,42 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et 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.
*/
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/limits.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/thread.h"
/**
* Defines user-owned signal stack for thread.
*/
errno_t pthread_attr_setsigaltstack_np(pthread_attr_t *attr, void *stackaddr,
size_t stacksize) {
if (!stackaddr) {
attr->__sigaltstackaddr = 0;
attr->__sigaltstacksize = 0;
return 0;
}
if (stacksize > INT_MAX)
return EINVAL;
if (stacksize < __get_minsigstksz())
return EINVAL;
attr->__sigaltstackaddr = stackaddr;
attr->__sigaltstacksize = stacksize;
return 0;
}

View file

@ -0,0 +1,78 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et 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.
*/
#include "libc/errno.h"
#include "libc/limits.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/thread.h"
/**
* Defines size of cosmo-owned signal stack for thread.
*
* The sigaltstack() function is useful for writing robust programs that
* can recover from the occasional thread having a stack overflow rather
* than having the entire process crash. To use it normally, sigaltstack
* needs to be called at the start of each thread with a unique piece of
* memory. However this is challenging to do *correctly* without support
* from the POSIX threads runtime, since canceled or crashed threads may
* need to execute on the signal stack during pthread_exit() which would
* prevent a thread-local storage key destructor from free()'ing it.
*
* By default pthread_create() will not install a sigaltstack() on newly
* created threads. If this function is called, on the attributes object
* that gets passed to pthread_create(), then it'll use malloc() to make
* a stack for the thread using the size you specify here. The threading
* runtime will also free that memory safely after complete termination.
*
* pthread_t id;
* pthread_attr_t attr;
* pthread_attr_init(&attr);
* pthread_attr_setguardsize(&attr, getpagesize());
* pthread_attr_setsigaltstacksize_np(&attr, stacksize);
* pthread_create(&id, &attr, func, 0);
* pthread_attr_destroy(&attr);
* pthread_join(id, 0);
*
* Try using a size of `sysconf(_SC_SIGSTKSZ)`. If you want the smallest
* size possible, then `sysconf(_SC_MINSIGSTKSZ) + 2048` is probably the
* smallest value that can reasonably expected to work with pthread_exit
*
* struct sigaction sa;
* sigemptyset(&sa.sa_mask);
* sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
* sa.sa_sigaction = on_crash_signal;
* sigaction(SIGSEGV, &sa, 0);
*
* Please note that in order for this to work, your handlers for signals
* such as SIGSEGV and SIGBUS need to use SA_ONSTACK in your sa_flags.
*
* @param stacksize contains stack size in bytes, or 0 to disable
* @return 0 on success, or errno on error
* @raise EINVAL if `stacksize` is less than `sysconf(_SC_MINSIGSTKSZ)`
*/
errno_t pthread_attr_setsigaltstacksize_np(pthread_attr_t *a,
size_t stacksize) {
if (stacksize) {
if (stacksize > INT_MAX)
return EINVAL;
if (stacksize < __get_minsigstksz())
return EINVAL;
}
a->__sigaltstacksize = stacksize;
return 0;
}

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/limits.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
/** /**
@ -71,6 +72,8 @@ errno_t pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr,
attr->__stacksize = 0; attr->__stacksize = 0;
return 0; return 0;
} }
if (stacksize > INT_MAX)
return EINVAL;
if (stacksize < PTHREAD_STACK_MIN) if (stacksize < PTHREAD_STACK_MIN)
return EINVAL; return EINVAL;
attr->__stackaddr = stackaddr; attr->__stackaddr = stackaddr;

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/limits.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
/** /**
@ -27,6 +28,8 @@
* @raise EINVAL if `stacksize` is less than `PTHREAD_STACK_MIN` * @raise EINVAL if `stacksize` is less than `PTHREAD_STACK_MIN`
*/ */
errno_t pthread_attr_setstacksize(pthread_attr_t *a, size_t stacksize) { errno_t pthread_attr_setstacksize(pthread_attr_t *a, size_t stacksize) {
if (stacksize > INT_MAX)
return EINVAL;
if (stacksize < PTHREAD_STACK_MIN) if (stacksize < PTHREAD_STACK_MIN)
return EINVAL; return EINVAL;
a->__stacksize = stacksize; a->__stacksize = stacksize;

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/struct/sigaltstack.h"
#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
@ -67,54 +68,104 @@ __static_yoink("_pthread_onfork_child");
#define MAP_ANON_OPENBSD 0x1000 #define MAP_ANON_OPENBSD 0x1000
#define MAP_STACK_OPENBSD 0x4000 #define MAP_STACK_OPENBSD 0x4000
void _pthread_free(struct PosixThread *pt, bool isfork) { void _pthread_free(struct PosixThread *pt) {
// thread must be removed from _pthread_list before calling
unassert(dll_is_alone(&pt->list) && &pt->list != _pthread_list); unassert(dll_is_alone(&pt->list) && &pt->list != _pthread_list);
// do nothing for the one and only magical statical posix thread
if (pt->pt_flags & PT_STATIC) if (pt->pt_flags & PT_STATIC)
return; return;
// unmap stack if the cosmo runtime was responsible for mapping it
if (pt->pt_flags & PT_OWNSTACK) if (pt->pt_flags & PT_OWNSTACK)
unassert(!munmap(pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize)); unassert(!munmap(pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize));
if (!isfork) {
uint64_t syshand = // free any additional upstream system resources
atomic_load_explicit(&pt->tib->tib_syshand, memory_order_acquire); // our fork implementation wipes this handle in child automatically
if (syshand) { uint64_t syshand =
if (IsWindows()) atomic_load_explicit(&pt->tib->tib_syshand, memory_order_acquire);
unassert(CloseHandle(syshand)); if (syshand) {
else if (IsXnuSilicon()) if (IsWindows())
__syslib->__pthread_join(syshand, 0); unassert(CloseHandle(syshand)); // non-inheritable
} else if (IsXnuSilicon())
unassert(!__syslib->__pthread_join(syshand, 0));
} }
// free heap memory associated with thread
if (pt->pt_flags & PT_OWNSIGALTSTACK)
free(pt->pt_attr.__sigaltstackaddr);
free(pt->pt_tls); free(pt->pt_tls);
free(pt); free(pt);
} }
void _pthread_decimate(void) { void _pthread_decimate(bool annihilation_only) {
struct Dll *e;
struct PosixThread *pt; struct PosixThread *pt;
struct Dll *e, *e2, *list = 0;
enum PosixThreadStatus status; enum PosixThreadStatus status;
StartOver:
// acquire posix threads gil
_pthread_lock(); _pthread_lock();
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) {
// swiftly remove every single zombie
// that isn't being held by a killing thread
for (e = dll_last(_pthread_list); e; e = e2) {
e2 = dll_prev(_pthread_list, e);
pt = POSIXTHREAD_CONTAINER(e); pt = POSIXTHREAD_CONTAINER(e);
if (atomic_load_explicit(&pt->pt_refs, memory_order_acquire) > 0)
continue; // pthread_kill() has a lease on this thread
status = atomic_load_explicit(&pt->pt_status, memory_order_acquire); status = atomic_load_explicit(&pt->pt_status, memory_order_acquire);
if (status != kPosixThreadZombie) if (status != kPosixThreadZombie)
break; break; // zombies only exist at the end of the linked list
if (!atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire)) { if (atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire))
dll_remove(&_pthread_list, e); continue; // undead thread should that'll stop existing soon
_pthread_unlock(); dll_remove(&_pthread_list, e);
_pthread_unref(pt); dll_make_first(&list, e);
goto StartOver;
}
} }
// code like pthread_exit() needs to call this in order to know if
// it's appropriate to run exit() handlers however we really don't
// want to have a thread exiting block on a bunch of __maps locks!
// therefore we only take action if we'll destroy all but the self
if (annihilation_only)
if (!(_pthread_list == _pthread_list->prev &&
_pthread_list == _pthread_list->next)) {
dll_make_last(&_pthread_list, list);
list = 0;
}
// release posix threads gil
_pthread_unlock(); _pthread_unlock();
// now free our thread local batch of zombies
// because death is a release and not a punishment
// this is advantaged by not holding locks over munmap
while ((e = dll_first(list))) {
pt = POSIXTHREAD_CONTAINER(e);
dll_remove(&list, e);
_pthread_free(pt);
}
} }
static int PosixThread(void *arg, int tid) { static int PosixThread(void *arg, int tid) {
void *rc; void *rc;
struct PosixThread *pt = arg; struct PosixThread *pt = arg;
// setup scheduling
if (pt->pt_attr.__inheritsched == PTHREAD_EXPLICIT_SCHED) { if (pt->pt_attr.__inheritsched == PTHREAD_EXPLICIT_SCHED) {
unassert(_weaken(_pthread_reschedule)); unassert(_weaken(_pthread_reschedule));
_weaken(_pthread_reschedule)(pt); // yoinked by attribute builder _weaken(_pthread_reschedule)(pt); // yoinked by attribute builder
} }
// setup signal stack
if (pt->pt_attr.__sigaltstacksize) {
struct sigaltstack ss;
ss.ss_sp = pt->pt_attr.__sigaltstackaddr;
ss.ss_size = pt->pt_attr.__sigaltstacksize;
ss.ss_flags = 0;
unassert(!sigaltstack(&ss, 0));
}
// set long jump handler so pthread_exit can bring control back here // set long jump handler so pthread_exit can bring control back here
if (!setjmp(pt->pt_exiter)) { if (!setjmp(pt->pt_exiter)) {
sigdelset(&pt->pt_attr.__sigmask, SIGTHR); sigdelset(&pt->pt_attr.__sigmask, SIGTHR);
@ -130,41 +181,45 @@ static int PosixThread(void *arg, int tid) {
// calling pthread_exit() will either jump back here, or call exit // calling pthread_exit() will either jump back here, or call exit
pthread_exit(rc); pthread_exit(rc);
} }
// avoid signal handler being triggered after we trash our own stack // avoid signal handler being triggered after we trash our own stack
__sig_block(); __sig_block();
// return to clone polyfill which clears tid, wakes futex, and exits // return to clone polyfill which clears tid, wakes futex, and exits
return 0; return 0;
} }
static int FixupCustomStackOnOpenbsd(pthread_attr_t *attr) { static bool TellOpenbsdThisIsStackMemory(void *addr, size_t size) {
// OpenBSD: Only permits RSP to occupy memory that's been explicitly return __sys_mmap(
// defined as stack memory. We need to squeeze the provided interval addr, size, PROT_READ | PROT_WRITE,
// in order to successfully call mmap(), which will return EINVAL if MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, -1,
// these calculations should overflow. 0, 0) == addr;
size_t n; }
uintptr_t x, y;
int e, rc, pagesz; // OpenBSD only permits RSP to occupy memory that's been explicitly
pagesz = __pagesize; // defined as stack memory, i.e. `lo <= %rsp < hi` must be the case
n = attr->__stacksize; static errno_t FixupCustomStackOnOpenbsd(pthread_attr_t *attr) {
x = (uintptr_t)attr->__stackaddr;
y = ROUNDUP(x, pagesz); // get interval
n -= y - x; uintptr_t lo = (uintptr_t)attr->__stackaddr;
n = ROUNDDOWN(n, pagesz); uintptr_t hi = lo + attr->__stacksize;
e = errno;
if (__sys_mmap((void *)y, n, PROT_READ | PROT_WRITE, // squeeze interval
MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, lo = (lo + __pagesize - 1) & -__pagesize;
-1, 0, 0) == (void *)y) { hi = hi & -__pagesize;
attr->__stackaddr = (void *)y;
attr->__stacksize = n; // tell os it's stack memory
return 0; errno_t olderr = errno;
} else { if (!TellOpenbsdThisIsStackMemory((void *)lo, hi - lo)) {
rc = errno; errno_t err = errno;
errno = e; errno = olderr;
if (rc == EOVERFLOW) { return err;
rc = EINVAL;
}
return rc;
} }
// update attributes with usable stack address
attr->__stackaddr = (void *)lo;
attr->__stacksize = hi - lo;
return 0;
} }
static errno_t pthread_create_impl(pthread_t *thread, static errno_t pthread_create_impl(pthread_t *thread,
@ -204,7 +259,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
// assume they know what they're doing as much as possible // assume they know what they're doing as much as possible
if (IsOpenbsd()) { if (IsOpenbsd()) {
if ((rc = FixupCustomStackOnOpenbsd(&pt->pt_attr))) { if ((rc = FixupCustomStackOnOpenbsd(&pt->pt_attr))) {
_pthread_free(pt, false); _pthread_free(pt);
return rc; return rc;
} }
} }
@ -214,21 +269,17 @@ static errno_t pthread_create_impl(pthread_t *thread,
pt->pt_attr.__guardsize = ROUNDUP(pt->pt_attr.__guardsize, pagesize); pt->pt_attr.__guardsize = ROUNDUP(pt->pt_attr.__guardsize, pagesize);
pt->pt_attr.__stacksize = pt->pt_attr.__stacksize; pt->pt_attr.__stacksize = pt->pt_attr.__stacksize;
if (pt->pt_attr.__guardsize + pagesize > pt->pt_attr.__stacksize) { if (pt->pt_attr.__guardsize + pagesize > pt->pt_attr.__stacksize) {
_pthread_free(pt, false); _pthread_free(pt);
return EINVAL; return EINVAL;
} }
pt->pt_attr.__stackaddr = pt->pt_attr.__stackaddr =
mmap(0, pt->pt_attr.__stacksize, PROT_READ | PROT_WRITE, mmap(0, pt->pt_attr.__stacksize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (pt->pt_attr.__stackaddr != MAP_FAILED) { if (pt->pt_attr.__stackaddr != MAP_FAILED) {
if (IsOpenbsd() && if (IsOpenbsd())
__sys_mmap( if (!TellOpenbsdThisIsStackMemory(pt->pt_attr.__stackaddr,
pt->pt_attr.__stackaddr, pt->pt_attr.__stacksize, pt->pt_attr.__stacksize))
PROT_READ | PROT_WRITE, notpossible;
MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD,
-1, 0, 0) != pt->pt_attr.__stackaddr) {
notpossible;
}
if (pt->pt_attr.__guardsize) if (pt->pt_attr.__guardsize)
if (mprotect(pt->pt_attr.__stackaddr, pt->pt_attr.__guardsize, if (mprotect(pt->pt_attr.__stackaddr, pt->pt_attr.__guardsize,
PROT_NONE | PROT_GUARD)) PROT_NONE | PROT_GUARD))
@ -236,7 +287,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
} }
if (!pt->pt_attr.__stackaddr || pt->pt_attr.__stackaddr == MAP_FAILED) { if (!pt->pt_attr.__stackaddr || pt->pt_attr.__stackaddr == MAP_FAILED) {
rc = errno; rc = errno;
_pthread_free(pt, false); _pthread_free(pt);
errno = e; errno = e;
if (rc == EINVAL || rc == EOVERFLOW) { if (rc == EINVAL || rc == EOVERFLOW) {
return EINVAL; return EINVAL;
@ -247,6 +298,18 @@ static errno_t pthread_create_impl(pthread_t *thread,
pt->pt_flags |= PT_OWNSTACK; pt->pt_flags |= PT_OWNSTACK;
} }
// setup signal stack
if (pt->pt_attr.__sigaltstacksize) {
if (!pt->pt_attr.__sigaltstackaddr) {
if (!(pt->pt_attr.__sigaltstackaddr =
malloc(pt->pt_attr.__sigaltstacksize))) {
_pthread_free(pt);
return errno;
}
pt->pt_flags |= PT_OWNSIGALTSTACK;
}
}
// set initial status // set initial status
pt->tib->tib_pthread = (pthread_t)pt; pt->tib->tib_pthread = (pthread_t)pt;
atomic_store_explicit(&pt->tib->tib_sigmask, -1, memory_order_relaxed); atomic_store_explicit(&pt->tib->tib_sigmask, -1, memory_order_relaxed);
@ -264,7 +327,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
memory_order_relaxed); memory_order_relaxed);
break; break;
default: default:
_pthread_free(pt, false); _pthread_free(pt);
return EINVAL; return EINVAL;
} }
@ -284,7 +347,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
_pthread_lock(); _pthread_lock();
dll_remove(&_pthread_list, &pt->list); dll_remove(&_pthread_list, &pt->list);
_pthread_unlock(); _pthread_unlock();
_pthread_free(pt, false); _pthread_free(pt);
return rc; return rc;
} }
@ -353,7 +416,7 @@ static const char *DescribeHandle(char buf[12], errno_t err, pthread_t *th) {
errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg) { void *(*start_routine)(void *), void *arg) {
errno_t err; errno_t err;
_pthread_decimate(); _pthread_decimate(false);
BLOCK_SIGNALS; BLOCK_SIGNALS;
err = pthread_create_impl(thread, attr, start_routine, arg, _SigMask); err = pthread_create_impl(thread, attr, start_routine, arg, _SigMask);
ALLOW_SIGNALS; ALLOW_SIGNALS;

View file

@ -32,6 +32,6 @@
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
*/ */
int pthread_decimate_np(void) { int pthread_decimate_np(void) {
_pthread_decimate(); _pthread_decimate(false);
return 0; return 0;
} }

View file

@ -40,10 +40,8 @@ static errno_t pthread_detach_impl(struct PosixThread *pt) {
if (atomic_compare_exchange_weak_explicit(&pt->pt_status, &status, if (atomic_compare_exchange_weak_explicit(&pt->pt_status, &status,
transition, memory_order_release, transition, memory_order_release,
memory_order_relaxed)) { memory_order_relaxed)) {
if (transition == kPosixThreadZombie) { if (transition == kPosixThreadZombie)
_pthread_zombify(pt); _pthread_zombify(pt);
}
_pthread_decimate();
return 0; return 0;
} }
} }

View file

@ -69,23 +69,33 @@
* @noreturn * @noreturn
*/ */
wontreturn void pthread_exit(void *rc) { wontreturn void pthread_exit(void *rc) {
int orphan;
struct CosmoTib *tib; struct CosmoTib *tib;
struct PosixThread *pt; struct PosixThread *pt;
enum PosixThreadStatus status, transition; enum PosixThreadStatus status, transition;
STRACE("pthread_exit(%p)", rc);
// get posix thread object
tib = __get_tls(); tib = __get_tls();
pt = (struct PosixThread *)tib->tib_pthread; pt = (struct PosixThread *)tib->tib_pthread;
pt->pt_flags |= PT_NOCANCEL;
pt->pt_rc = rc;
STRACE("pthread_exit(%p)", rc); // "The behavior of pthread_exit() is undefined if called from a
// cancellation cleanup handler or destructor function that was
// invoked as a result of either an implicit or explicit call to
// pthread_exit()." ──Quoth POSIX.1-2017
unassert(!(pt->pt_flags & PT_EXITING));
// set state
pt->pt_flags |= PT_NOCANCEL | PT_EXITING;
pt->pt_rc = rc;
// free resources // free resources
__cxa_thread_finalize(); __cxa_thread_finalize();
_pthread_decimate();
// run atexit handlers if orphaned thread // run atexit handlers if orphaned thread
if (pthread_orphan_np()) _pthread_decimate(true);
if ((orphan = pthread_orphan_np()))
if (_weaken(__cxa_finalize)) if (_weaken(__cxa_finalize))
_weaken(__cxa_finalize)(NULL); _weaken(__cxa_finalize)(NULL);
@ -113,8 +123,11 @@ wontreturn void pthread_exit(void *rc) {
if (transition == kPosixThreadZombie) if (transition == kPosixThreadZombie)
_pthread_zombify(pt); _pthread_zombify(pt);
// check if this is the last survivor // "The process shall exit with an exit status of 0 after the last
if (pthread_orphan_np()) { // thread has been terminated. The behavior shall be as if the
// implementation called exit() with a zero argument at thread
// termination time." ──Quoth POSIX.1-2017
if (orphan) {
for (const uintptr_t *p = __fini_array_end; p > __fini_array_start;) for (const uintptr_t *p = __fini_array_end; p > __fini_array_start;)
((void (*)(void))(*--p))(); ((void (*)(void))(*--p))();
_Exit(0); _Exit(0);

View file

@ -55,29 +55,38 @@ static const char *DescribeReturnValue(char buf[30], int err, void **value) {
* *
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @raise ECANCELED if calling thread was cancelled in masked mode * @raise ECANCELED if calling thread was cancelled in masked mode
* @raise EDEADLK if `ctid` refers calling thread's own ctid futex
* @raise EBUSY if `abstime` was specified and deadline expired * @raise EBUSY if `abstime` was specified and deadline expired
* @cancelationpoint * @cancelationpoint
*/ */
static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) { static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) {
int x, e, rc = 0; int x, e;
unassert(ctid != &__get_tls()->tib_tid); errno_t err = 0;
// "If the thread calling pthread_join() is canceled, then the target if (ctid == &__get_tls()->tib_tid) {
// thread shall not be detached." ──Quoth POSIX.1-2017 // "If an implementation detects that the value specified by the
if (!(rc = pthread_testcancel_np())) { // thread argument to pthread_join() refers to the calling thread,
BEGIN_CANCELATION_POINT; // it is recommended that the function should fail and report an
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { // [EDEADLK] error." ──Quoth POSIX.1-2017
e = nsync_futex_wait_(ctid, x, !IsWindows() && !IsXnu(), abstime); err = EDEADLK;
if (e == -ECANCELED) { } else {
rc = ECANCELED; // "If the thread calling pthread_join() is canceled, then the target
break; // thread shall not be detached." ──Quoth POSIX.1-2017
} else if (e == -ETIMEDOUT) { if (!(err = pthread_testcancel_np())) {
rc = EBUSY; BEGIN_CANCELATION_POINT;
break; while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
e = nsync_futex_wait_(ctid, x, !IsWindows() && !IsXnu(), abstime);
if (e == -ECANCELED) {
err = ECANCELED;
break;
} else if (e == -ETIMEDOUT) {
err = EBUSY;
break;
}
} }
END_CANCELATION_POINT;
} }
END_CANCELATION_POINT;
} }
return rc; return err;
} }
/** /**
@ -97,6 +106,7 @@ static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) {
* when we'll stop waiting; if this is null we will wait forever * when we'll stop waiting; if this is null we will wait forever
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @raise ECANCELED if calling thread was cancelled in masked mode * @raise ECANCELED if calling thread was cancelled in masked mode
* @raise EDEADLK if `thread` refers to calling thread
* @raise EBUSY if `abstime` deadline elapsed * @raise EBUSY if `abstime` deadline elapsed
* @cancelationpoint * @cancelationpoint
* @returnserrno * @returnserrno
@ -104,26 +114,29 @@ static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) {
errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr, errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr,
struct timespec *abstime) { struct timespec *abstime) {
int tid; int tid;
errno_t err; errno_t err = 0;
struct PosixThread *pt; struct PosixThread *pt;
enum PosixThreadStatus status; enum PosixThreadStatus status;
pt = (struct PosixThread *)thread; pt = (struct PosixThread *)thread;
tid = _pthread_tid(pt);
unassert(_pthread_tid(pt));
status = atomic_load_explicit(&pt->pt_status, memory_order_acquire);
// "The behavior is undefined if the value specified by the thread // "The behavior is undefined if the value specified by the thread
// argument to pthread_join() does not refer to a joinable thread." // argument to pthread_join() does not refer to a joinable thread."
// ──Quoth POSIX.1-2017 // ──Quoth POSIX.1-2017
unassert((tid = _pthread_tid(pt)));
status = atomic_load_explicit(&pt->pt_status, memory_order_acquire);
unassert(status == kPosixThreadJoinable || status == kPosixThreadTerminated); unassert(status == kPosixThreadJoinable || status == kPosixThreadTerminated);
// "The results of multiple simultaneous calls to pthread_join()
// specifying the same target thread are undefined."
// ──Quoth POSIX.1-2017
if (!(err = _pthread_wait(&pt->tib->tib_tid, abstime))) { if (!(err = _pthread_wait(&pt->tib->tib_tid, abstime))) {
_pthread_lock(); atomic_store_explicit(&pt->pt_status, kPosixThreadZombie,
dll_remove(&_pthread_list, &pt->list); memory_order_release);
_pthread_unlock(); _pthread_zombify(pt);
if (value_ptr) { if (value_ptr)
*value_ptr = pt->pt_rc; *value_ptr = pt->pt_rc;
}
_pthread_unref(pt);
} }
STRACE("pthread_timedjoin_np(%d, %s, %s) → %s", tid, STRACE("pthread_timedjoin_np(%d, %s, %s) → %s", tid,
DescribeReturnValue(alloca(30), err, value_ptr), DescribeReturnValue(alloca(30), err, value_ptr),
DescribeTimespec(err ? -1 : 0, abstime), DescribeErrno(err)); DescribeTimespec(err ? -1 : 0, abstime), DescribeErrno(err));

View file

@ -94,9 +94,11 @@ typedef struct pthread_attr_s {
int __schedpolicy; int __schedpolicy;
int __contentionscope; int __contentionscope;
int __guardsize; int __guardsize;
size_t __stacksize; int __stacksize;
int __sigaltstacksize;
uint64_t __sigmask; uint64_t __sigmask;
void *__stackaddr; void *__stackaddr;
void *__sigaltstackaddr;
} pthread_attr_t; } pthread_attr_t;
struct _pthread_cleanup_buffer { struct _pthread_cleanup_buffer {
@ -117,6 +119,8 @@ int pthread_attr_getschedpolicy(const pthread_attr_t *, int *) libcesque paramsn
int pthread_attr_getscope(const pthread_attr_t *, int *) libcesque paramsnonnull(); int pthread_attr_getscope(const pthread_attr_t *, int *) libcesque paramsnonnull();
int pthread_attr_getstack(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull(); int pthread_attr_getstack(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull();
int pthread_attr_getstacksize(const pthread_attr_t *, size_t *) libcesque paramsnonnull(); int pthread_attr_getstacksize(const pthread_attr_t *, size_t *) libcesque paramsnonnull();
int pthread_attr_getsigaltstack_np(const pthread_attr_t *, void **, size_t *) libcesque paramsnonnull();
int pthread_attr_getsigaltstacksize_np(const pthread_attr_t *, size_t *) libcesque paramsnonnull();
int pthread_attr_init(pthread_attr_t *) libcesque paramsnonnull(); int pthread_attr_init(pthread_attr_t *) libcesque paramsnonnull();
int pthread_attr_setdetachstate(pthread_attr_t *, int) libcesque paramsnonnull(); int pthread_attr_setdetachstate(pthread_attr_t *, int) libcesque paramsnonnull();
int pthread_attr_setguardsize(pthread_attr_t *, size_t) libcesque paramsnonnull(); int pthread_attr_setguardsize(pthread_attr_t *, size_t) libcesque paramsnonnull();
@ -125,6 +129,8 @@ int pthread_attr_setschedpolicy(pthread_attr_t *, int) libcesque paramsnonnull()
int pthread_attr_setscope(pthread_attr_t *, int) libcesque paramsnonnull(); int pthread_attr_setscope(pthread_attr_t *, int) libcesque paramsnonnull();
int pthread_attr_setstack(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1)); int pthread_attr_setstack(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1));
int pthread_attr_setstacksize(pthread_attr_t *, size_t) libcesque paramsnonnull(); int pthread_attr_setstacksize(pthread_attr_t *, size_t) libcesque paramsnonnull();
int pthread_attr_setsigaltstack_np(pthread_attr_t *, void *, size_t) libcesque paramsnonnull((1));
int pthread_attr_setsigaltstacksize_np(pthread_attr_t *, size_t);
int pthread_barrier_destroy(pthread_barrier_t *) libcesque paramsnonnull(); int pthread_barrier_destroy(pthread_barrier_t *) libcesque paramsnonnull();
int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *, unsigned) libcesque paramsnonnull((1)); int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *, unsigned) libcesque paramsnonnull((1));
int pthread_barrier_wait(pthread_barrier_t *) libcesque paramsnonnull(); int pthread_barrier_wait(pthread_barrier_t *) libcesque paramsnonnull();

View file

@ -0,0 +1,86 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
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
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 <cosmo.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
/**
* stack overflow recovery technique #5
* use the cosmo posix threads extensions
*/
sig_atomic_t smashed_stack;
void CrashHandler(int sig) {
smashed_stack = true;
pthread_exit(0);
}
int StackOverflow(int f(), int n) {
if (n < INT_MAX) {
return f(f, n + 1) - 1;
} else {
return INT_MAX;
}
}
int (*pStackOverflow)(int (*)(), int) = StackOverflow;
void *MyPosixThread(void *arg) {
exit(pStackOverflow(pStackOverflow, 0));
return 0;
}
int main() {
// choose the most dangerously small size possible
size_t sigstacksize = sysconf(_SC_MINSIGSTKSZ) + 2048;
// setup signal handler
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK;
sa.sa_handler = CrashHandler;
if (sigaction(SIGBUS, &sa, 0))
return 1;
if (sigaction(SIGSEGV, &sa, 0))
return 2;
// create thread with signal stack
pthread_t id;
pthread_attr_t attr;
if (pthread_attr_init(&attr))
return 3;
if (pthread_attr_setguardsize(&attr, getpagesize()))
return 4;
if (pthread_attr_setsigaltstacksize_np(&attr, sigstacksize))
return 5;
if (pthread_create(&id, &attr, MyPosixThread, 0))
return 6;
if (pthread_attr_destroy(&attr))
return 7;
if (pthread_join(id, 0))
return 8;
if (!smashed_stack)
return 9;
CheckForMemoryLeaks();
}

View file

@ -50,12 +50,10 @@ TEST(cosmo_once, test) {
pthread_t th[N]; pthread_t th[N];
x = y = 0; x = y = 0;
ASSERT_EQ(0, pthread_barrier_init(&b, 0, N)); ASSERT_EQ(0, pthread_barrier_init(&b, 0, N));
for (i = 0; i < N; ++i) { for (i = 0; i < N; ++i)
ASSERT_EQ(0, pthread_create(th + i, 0, Worker, 0)); ASSERT_EQ(0, pthread_create(th + i, 0, Worker, 0));
} for (i = 0; i < N; ++i)
for (i = 0; i < N; ++i) {
ASSERT_EQ(0, pthread_join(th[i], 0)); ASSERT_EQ(0, pthread_join(th[i], 0));
}
ASSERT_EQ(N, atomic_load(&x)); ASSERT_EQ(N, atomic_load(&x));
ASSERT_EQ(1, atomic_load(&y)); ASSERT_EQ(1, atomic_load(&y));
ASSERT_EQ(0, pthread_barrier_destroy(&b)); ASSERT_EQ(0, pthread_barrier_destroy(&b));

View file

@ -528,18 +528,21 @@ TEST(mmap, sharedFileMapFork) {
int count; int count;
void *ptrs[N]; void *ptrs[N];
size_t sizes[N];
void BenchMmapPrivate(void) { void BenchMmapPrivate(void) {
void *p; void *p;
p = mmap(0, gransz * 10, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, p = mmap(0, (sizes[count] = rand() % (pagesz * 500)), PROT_READ | PROT_WRITE,
-1, 0); MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (p == MAP_FAILED) if (p == MAP_FAILED)
__builtin_trap(); __builtin_trap();
ptrs[count++] = p; ptrs[count] = p;
++count;
} }
void BenchUnmap(void) { void BenchUnmap(void) {
if (munmap(ptrs[--count], gransz * 10)) --count;
if (munmap(ptrs[count], sizes[count]))
__builtin_trap(); __builtin_trap();
} }
@ -557,7 +560,7 @@ void BenchBigMunmap(void) {
__builtin_trap(); __builtin_trap();
} }
BENCH(mmap, bench) { TEST(mmap, bench) {
EZBENCH2("mmap", donothing, BenchMmapPrivate()); EZBENCH2("mmap", donothing, BenchMmapPrivate());
EZBENCH2("munmap", donothing, BenchUnmap()); EZBENCH2("munmap", donothing, BenchUnmap());
// EZBENCH2("big mmap", donothing, BenchBigMmap()); // EZBENCH2("big mmap", donothing, BenchBigMmap());

View file

@ -0,0 +1,40 @@
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
int got_cleanup;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
void cleanup(void* arg) {
got_cleanup = 1;
}
void* worker(void* arg) {
pthread_cleanup_push(cleanup, 0);
if (pthread_mutex_lock(&mu))
_Exit(11);
pthread_cond_wait(&cv, &mu);
_Exit(12);
pthread_cleanup_pop(0);
}
int main(int argc, char* argv[]) {
void* rc;
pthread_t th;
if (pthread_create(&th, 0, worker, 0))
return 2;
if (pthread_cancel(th))
return 3;
if (pthread_join(th, &rc))
return 4;
if (rc != PTHREAD_CANCELED)
return 5;
if (!got_cleanup)
return 6;
if (pthread_mutex_trylock(&mu) != EBUSY)
return 7;
if (pthread_mutex_unlock(&mu))
return 8;
}

View file

@ -0,0 +1,43 @@
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
int got_cleanup;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
void cleanup(void* arg) {
got_cleanup = 1;
}
void* worker(void* arg) {
if (pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0))
_Exit(10);
pthread_cleanup_push(cleanup, 0);
if (pthread_mutex_lock(&mu))
_Exit(11);
if (pthread_cond_wait(&cv, &mu) != ECANCELED)
_Exit(12);
if (pthread_mutex_trylock(&mu) != EBUSY)
_Exit(13);
if (pthread_mutex_unlock(&mu))
_Exit(14);
pthread_cleanup_pop(0);
return (void*)123;
}
int main(int argc, char* argv[]) {
void* rc;
pthread_t th;
if (pthread_create(&th, 0, worker, 0))
return 2;
if (pthread_cancel(th))
return 3;
if (pthread_join(th, &rc))
return 4;
if (rc != (void*)123)
return 5;
if (got_cleanup)
return 6;
}

View file

@ -0,0 +1,41 @@
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
int pfds[2];
int got_cleanup;
void cleanup(void* arg) {
got_cleanup = 1;
}
void* worker(void* arg) {
char buf[8];
if (pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0))
_Exit(10);
pthread_cleanup_push(cleanup, 0);
if (read(pfds[0], buf, sizeof(buf)) != -1)
_Exit(11);
if (errno != ECANCELED)
_Exit(12);
pthread_cleanup_pop(0);
return (void*)123;
}
int main(int argc, char* argv[]) {
void* rc;
pthread_t th;
if (pipe(pfds))
return 1;
if (pthread_create(&th, 0, worker, 0))
return 2;
if (pthread_cancel(th))
return 3;
if (pthread_join(th, &rc))
return 4;
if (rc != (void*)123)
return 5;
if (got_cleanup)
return 7;
}

View file

@ -0,0 +1,30 @@
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
sigset_t parent_mask;
sigset_t child_mask;
void* worker(void* arg) {
if (pthread_sigmask(SIG_SETMASK, 0, &child_mask))
_Exit(1);
return 0;
}
int main(int argc, char* argv[]) {
pthread_t th;
sigemptyset(&parent_mask);
sigaddset(&parent_mask, SIGSYS);
sigaddset(&parent_mask, SIGUSR2);
sigaddset(&parent_mask, SIGWINCH);
if (pthread_sigmask(SIG_SETMASK, &parent_mask, 0))
return 1;
if (pthread_create(&th, 0, worker, 0))
return 2;
if (pthread_join(th, 0))
return 3;
for (int i = 1; i <= _NSIG; ++i)
if (sigismember(&parent_mask, i) != sigismember(&child_mask, i))
return 4;
}

View file

@ -34,6 +34,7 @@
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h" #include "libc/runtime/stack.h"
#include "libc/runtime/sysconf.h" #include "libc/runtime/sysconf.h"
#include "libc/stdio/rand.h"
#include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sched.h" #include "libc/sysv/consts/sched.h"
@ -279,11 +280,10 @@ static void CreateDetached(void) {
ASSERT_EQ(0, pthread_attr_destroy(&attr)); ASSERT_EQ(0, pthread_attr_destroy(&attr));
} }
BENCH(pthread_create, bench) { TEST(pthread_create, bench) {
EZBENCH2("CreateJoin", donothing, CreateJoin()); EZBENCH2("CreateJoin", donothing, CreateJoin());
EZBENCH2("CreateDetach", donothing, CreateDetach()); EZBENCH2("CreateDetach", donothing, CreateDetach());
EZBENCH2("CreateDetached", donothing, CreateDetached()); EZBENCH2("CreateDetached", donothing, CreateDetached());
while (!pthread_orphan_np()) { while (!pthread_orphan_np())
_pthread_decimate(); pthread_decimate_np();
}
} }

View file

@ -50,9 +50,8 @@ TEST(pthread_detach, testCreateReturn) {
pthread_t id; pthread_t id;
ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0)); ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0));
ASSERT_EQ(0, pthread_detach(id)); ASSERT_EQ(0, pthread_detach(id));
while (!pthread_orphan_np()) { while (!pthread_orphan_np())
_pthread_decimate(); pthread_decimate_np();
}
} }
TEST(pthread_detach, testDetachUponCreation) { TEST(pthread_detach, testDetachUponCreation) {
@ -62,7 +61,6 @@ TEST(pthread_detach, testDetachUponCreation) {
ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
ASSERT_EQ(0, pthread_create(&th, &attr, Increment, 0)); ASSERT_EQ(0, pthread_create(&th, &attr, Increment, 0));
ASSERT_EQ(0, pthread_attr_destroy(&attr)); ASSERT_EQ(0, pthread_attr_destroy(&attr));
while (!pthread_orphan_np()) { while (!pthread_orphan_np())
_pthread_decimate(); pthread_decimate_np();
}
} }