From 4a6fd3d9103f2350359388ab6c4311bca34d25c4 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 8 Oct 2022 23:54:05 -0700 Subject: [PATCH] Make more improvements to threading support - fix rare thread exit race condition on openbsd - pthread_getattr_np() now supplies detached status - child threads may now pthread_join() the main thread - introduce sigandset(), sigorset(), and sigisemptyset() - introduce pthread_cleanup_push() and pthread_cleanup_pop() --- libc/assert.h | 12 +-- libc/calls/struct/sigset.h | 3 + libc/intrin/exit1.greg.c | 7 +- libc/intrin/sigandset.c | 36 ++++++++ libc/intrin/sigisemptyset.c | 38 +++++++++ libc/intrin/sigorset.c | 36 ++++++++ libc/log/checkfail.c | 7 +- libc/log/memlog.c | 1 + libc/log/oncrash.c | 4 +- libc/nexgen32e/gc.S | 1 + libc/runtime/clone.c | 41 ++++----- libc/runtime/getpagesize.S | 2 + libc/stdio/flockfile.c | 1 + libc/stdio/ftrylockfile.c | 1 + libc/stdio/funlockfile.c | 1 + libc/thread/posixthread.internal.h | 12 ++- libc/thread/pthread_attr_getdetachstate.c | 2 +- libc/thread/pthread_attr_getguardsize.c | 2 +- libc/thread/pthread_attr_getinheritsched.c | 2 +- libc/thread/pthread_attr_getschedparam.c | 2 +- libc/thread/pthread_attr_getschedpolicy.c | 2 +- libc/thread/pthread_attr_getscope.c | 2 +- libc/thread/pthread_attr_getstack.c | 4 +- libc/thread/pthread_attr_getstacksize.c | 4 +- libc/thread/pthread_attr_init.c | 4 +- libc/thread/pthread_attr_setdetachstate.c | 2 +- libc/thread/pthread_attr_setguardsize.c | 2 +- libc/thread/pthread_attr_setinheritsched.c | 2 +- libc/thread/pthread_attr_setschedparam.c | 2 +- libc/thread/pthread_attr_setschedpolicy.c | 2 +- libc/thread/pthread_attr_setscope.c | 2 +- libc/thread/pthread_attr_setstack.c | 8 +- libc/thread/pthread_attr_setstacksize.c | 2 +- libc/thread/pthread_cleanup.c | 38 +++++++++ libc/thread/pthread_cleanup_pop.c | 30 +++++++ libc/thread/pthread_cleanup_push.c | 29 +++++++ libc/thread/pthread_create.c | 85 ++++++++++--------- libc/thread/pthread_exit.c | 67 ++++++++++++--- libc/thread/pthread_getattr_np.c | 39 +++++++++ libc/thread/pthread_getschedparam.c | 4 +- libc/thread/pthread_join.c | 6 +- libc/thread/pthread_main.c | 31 +++++++ libc/thread/pthread_reschedule.c | 9 +- libc/thread/pthread_self.c | 11 +-- libc/thread/pthread_setschedparam.c | 4 +- libc/thread/pthread_ungarbage.c | 8 +- libc/thread/thread.h | 35 ++++++-- libc/thread/tls.h | 3 +- libc/thread/wait0.c | 79 ++---------------- net/turfwar/turfwar.c | 2 +- test/libc/calls/pledge_test.c | 1 + test/libc/thread/pthread_create_test.c | 97 +++++++++++++++++++--- 52 files changed, 586 insertions(+), 241 deletions(-) create mode 100644 libc/intrin/sigandset.c create mode 100644 libc/intrin/sigisemptyset.c create mode 100644 libc/intrin/sigorset.c create mode 100644 libc/thread/pthread_cleanup.c create mode 100644 libc/thread/pthread_cleanup_pop.c create mode 100644 libc/thread/pthread_cleanup_push.c create mode 100644 libc/thread/pthread_main.c diff --git a/libc/assert.h b/libc/assert.h index 5850a574f..e505fbba4 100644 --- a/libc/assert.h +++ b/libc/assert.h @@ -2,6 +2,7 @@ #define COSMOPOLITAN_LIBC_ASSERT_H_ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +#include "libc/intrin/kprintf.h" extern bool __assert_disable; void __assert_fail(const char *, const char *, int) hidden relegated; @@ -23,11 +24,12 @@ void __assert_fail(const char *, const char *, int) hidden relegated; } \ } while (0) -#define _npassert(x) \ - do { \ - if (__builtin_expect(!(x), 0)) { \ - notpossible; \ - } \ +#define _npassert(x) \ + do { \ + if (__builtin_expect(!(x), 0)) { \ + kprintf("%s:%d: oh no!\n", __FILE__, __LINE__); \ + notpossible; \ + } \ } while (0) COSMOPOLITAN_C_END_ diff --git a/libc/calls/struct/sigset.h b/libc/calls/struct/sigset.h index f3ac87163..7e52c0c0d 100644 --- a/libc/calls/struct/sigset.h +++ b/libc/calls/struct/sigset.h @@ -11,6 +11,9 @@ int sigaddset(sigset_t *, int) paramsnonnull(); int sigdelset(sigset_t *, int) paramsnonnull(); int sigemptyset(sigset_t *) paramsnonnull(); int sigfillset(sigset_t *) paramsnonnull(); +int sigandset(sigset_t *, const sigset_t *, const sigset_t *) paramsnonnull(); +int sigorset(sigset_t *, const sigset_t *, const sigset_t *) paramsnonnull(); +int sigisemptyset(const sigset_t *) paramsnonnull(); int sigismember(const sigset_t *, int) paramsnonnull() nosideeffect; int sigprocmask(int, const sigset_t *, sigset_t *); int sigsuspend(const sigset_t *); diff --git a/libc/intrin/exit1.greg.c b/libc/intrin/exit1.greg.c index 098a10658..ad24310e2 100644 --- a/libc/intrin/exit1.greg.c +++ b/libc/intrin/exit1.greg.c @@ -16,19 +16,24 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/strace.internal.h" #include "libc/dce.h" #include "libc/intrin/asmflag.h" #include "libc/intrin/promises.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/nt/thread.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/nr.h" +#include "libc/thread/tls.h" __msabi extern typeof(ExitThread) *const __imp_ExitThread; /** * Terminates thread with raw system call. * + * The function you want is pthread_exit(). If you call this function + * whilst using the pthreads then your joiners might not get woken up + * on non-Linux platforms where we zero __get_tls()->tib_tid manually + * * If this is the main thread, or an orphaned child thread, then this * function is equivalent to exiting the process; however, `rc` shall * only be reported to the parent process on Linux, FreeBSD & Windows diff --git a/libc/intrin/sigandset.c b/libc/intrin/sigandset.c new file mode 100644 index 000000000..816a19275 --- /dev/null +++ b/libc/intrin/sigandset.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/sigset.h" +#include "libc/macros.internal.h" +#include "libc/str/str.h" + +/** + * Bitwise ANDs two signal sets. + * + * @return 0 on success, or -1 w/ errno + * @asyncsignalsafe + * @vforksafe + */ +int sigandset(sigset_t *set, const sigset_t *x, const sigset_t *y) { + int i; + for (i = 0; i < ARRAYLEN(set->__bits); ++i) { + set->__bits[i] = x->__bits[i] & y->__bits[i]; + } + return 0; +} diff --git a/libc/intrin/sigisemptyset.c b/libc/intrin/sigisemptyset.c new file mode 100644 index 000000000..97da1e5e1 --- /dev/null +++ b/libc/intrin/sigisemptyset.c @@ -0,0 +1,38 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/sigset.h" +#include "libc/macros.internal.h" +#include "libc/str/str.h" + +/** + * Determines if signal set is empty. + * + * @return 1 if empty, 0 if non-empty, or -1 w/ errno + * @asyncsignalsafe + * @vforksafe + */ +int sigisemptyset(const sigset_t *set) { + int i; + for (i = 0; i < ARRAYLEN(set->__bits); ++i) { + if (set->__bits[i]) { + return 0; + } + } + return 1; +} diff --git a/libc/intrin/sigorset.c b/libc/intrin/sigorset.c new file mode 100644 index 000000000..365e16b77 --- /dev/null +++ b/libc/intrin/sigorset.c @@ -0,0 +1,36 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/sigset.h" +#include "libc/macros.internal.h" +#include "libc/str/str.h" + +/** + * Bitwise ORs two signal sets. + * + * @return 0 on success, or -1 w/ errno + * @asyncsignalsafe + * @vforksafe + */ +int sigorset(sigset_t *set, const sigset_t *x, const sigset_t *y) { + int i; + for (i = 0; i < ARRAYLEN(set->__bits); ++i) { + set->__bits[i] = x->__bits[i] | y->__bits[i]; + } + return 0; +} diff --git a/libc/log/checkfail.c b/libc/log/checkfail.c index f8013fc52..4ed5796b6 100644 --- a/libc/log/checkfail.c +++ b/libc/log/checkfail.c @@ -16,12 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/safemacros.internal.h" #include "libc/calls/calls.h" -#include "libc/intrin/strace.internal.h" #include "libc/errno.h" #include "libc/fmt/fmt.h" #include "libc/intrin/kprintf.h" +#include "libc/intrin/safemacros.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/log/check.h" #include "libc/log/color.internal.h" #include "libc/log/internal.h" @@ -71,9 +71,6 @@ relegated void __check_fail(const char *suffix, const char *opstr, kprintf(" %s", __argv[i]); } kprintf("%s\n", RESET); - if (!IsTiny() && e == ENOMEM) { - __print_maps(); - } __die(); unreachable; } diff --git a/libc/log/memlog.c b/libc/log/memlog.c index 1b8dfb4d2..af7017072 100644 --- a/libc/log/memlog.c +++ b/libc/log/memlog.c @@ -26,6 +26,7 @@ #include "libc/mem/mem.h" #include "libc/runtime/symbols.internal.h" #include "libc/sysv/consts/o.h" +#include "libc/thread/thread.h" #include "third_party/dlmalloc/dlmalloc.h" /** diff --git a/libc/log/oncrash.c b/libc/log/oncrash.c index 733770b1a..d6ae1a992 100644 --- a/libc/log/oncrash.c +++ b/libc/log/oncrash.c @@ -223,10 +223,10 @@ relegated void ShowCrashReport(int err, int sig, struct siginfo *si, host, getpid(), gettid(), program_invocation_name, names.sysname, names.version, names.nodename, names.release); if (ctx) { - kprintf("\n"); - ShowFunctionCalls(ctx); ShowGeneralRegisters(ctx); ShowSseRegisters(ctx); + kprintf("\n"); + ShowFunctionCalls(ctx); } kprintf("\n"); __print_maps(); diff --git a/libc/nexgen32e/gc.S b/libc/nexgen32e/gc.S index d9a41adff..04b2ed68f 100644 --- a/libc/nexgen32e/gc.S +++ b/libc/nexgen32e/gc.S @@ -57,4 +57,5 @@ __gc: mov %fs:0,%rcx # __get_tls() leave ret 9: ud2 + nop .endfn __gc,globl,hidden diff --git a/libc/runtime/clone.c b/libc/runtime/clone.c index 3c84277a4..b5fc9aa1f 100644 --- a/libc/runtime/clone.c +++ b/libc/runtime/clone.c @@ -240,12 +240,14 @@ static wontreturn void FreebsdThreadMain(void *p) { // we no longer use the stack after this point // void thr_exit(%rdi = long *state); asm volatile("movl\t$0,%0\n\t" // *wt->ztid = 0 - "syscall\n\t" // _umtx_op() - "movl\t$431,%%eax\n\t" // thr_exit() - "xor\t%%edi,%%edi\n\t" - "syscall" + "syscall\n\t" // _umtx_op(wt->ztid, WAKE, INT_MAX) + "movl\t$431,%%eax\n\t" // thr_exit(long *nonzeroes_and_wake) + "xor\t%%edi,%%edi\n\t" // sad we can't use this free futex op + "syscall\n\t" // exit1() fails if thread is orphaned + "movl\t$1,%%eax\n\t" // exit() + "syscall" // : "=m"(*wt->ztid) - : "a"(454), "D"(wt->ztid), "S"(UMTX_OP_WAKE) + : "a"(454), "D"(wt->ztid), "S"(UMTX_OP_WAKE), "d"(INT_MAX) : "rcx", "r8", "r9", "r10", "r11", "memory"); notpossible; } @@ -289,15 +291,6 @@ static int CloneFreebsd(int (*func)(void *, int), char *stk, size_t stksz, //////////////////////////////////////////////////////////////////////////////// // OPEN BESIYATA DISHMAYA -static void *oldrsp; - -__attribute__((__constructor__)) static void OpenbsdGetSafeRsp(void) { - // main thread stack should never be freed during process lifetime. we - // won't actually change this stack below. we just need need a place - // where threads can park RSP for a few instructions while dying. - oldrsp = __builtin_frame_address(0); -} - // we can't use address sanitizer because: // 1. __asan_handle_no_return wipes stack [todo?] noasan static wontreturn void OpenbsdThreadMain(void *p) { @@ -305,21 +298,15 @@ noasan static wontreturn void OpenbsdThreadMain(void *p) { *wt->ptid = wt->tid; *wt->ctid = wt->tid; wt->func(wt->arg, wt->tid); - // we no longer use the stack after this point. however openbsd - // validates the rsp register too so a race condition can still - // happen if the parent tries to free the stack. we'll solve it - // by simply changing rsp back to the old value before exiting! - // although ideally there should be a better solution. - // - // void __threxit(%rdi = int32_t *notdead); - asm volatile("mov\t%2,%%rsp\n\t" - "movl\t$0,(%%rdi)\n\t" // *wt->ztid = 0 - "syscall\n\t" // futex() - "mov\t$302,%%eax\n\t" // threxit() + asm volatile("mov\t%2,%%rsp\n\t" // so syscall can validate stack exists + "movl\t$0,(%%rdi)\n\t" // *wt->ztid = 0 (old stack now free'd) + "syscall\n\t" // futex(int*, op, val) will wake wait0 + "xor\t%%edi,%%edi\n\t" // so kernel doesn't write to old stack + "mov\t$302,%%eax\n\t" // __threxit(int *notdead) doesn't wake "syscall" : "=m"(*wt->ztid) - : "a"(83), "m"(oldrsp), "D"(wt->ztid), "S"(FUTEX_WAKE), - "d"(INT_MAX) + : "a"(83), "m"(__oldstack), "D"(wt->ztid), + "S"(2 /* FUTEX_WAKE */), "d"(INT_MAX) : "rcx", "r11", "memory"); notpossible; } diff --git a/libc/runtime/getpagesize.S b/libc/runtime/getpagesize.S index 0db4acd92..c050392f5 100644 --- a/libc/runtime/getpagesize.S +++ b/libc/runtime/getpagesize.S @@ -19,6 +19,8 @@ #include "libc/macros.internal.h" // Returns granularity of memory manager. +// +// @see sysconf(_SC_PAGE_SIZE) which is portable getpagesize: .leafprologue .profilable diff --git a/libc/stdio/flockfile.c b/libc/stdio/flockfile.c index 852459e73..903f1f24e 100644 --- a/libc/stdio/flockfile.c +++ b/libc/stdio/flockfile.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/lock.internal.h" #include "libc/stdio/stdio.h" +#include "libc/thread/thread.h" /** * Acquires reentrant lock on stdio object, blocking if needed. diff --git a/libc/stdio/ftrylockfile.c b/libc/stdio/ftrylockfile.c index e29a2c28c..a8c998ef2 100644 --- a/libc/stdio/ftrylockfile.c +++ b/libc/stdio/ftrylockfile.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/lock.internal.h" #include "libc/stdio/stdio.h" +#include "libc/thread/thread.h" /** * Tries to acquire reentrant stdio object lock. diff --git a/libc/stdio/funlockfile.c b/libc/stdio/funlockfile.c index 20a9358f6..6f14f81b2 100644 --- a/libc/stdio/funlockfile.c +++ b/libc/stdio/funlockfile.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/stdio/lock.internal.h" #include "libc/stdio/stdio.h" +#include "libc/thread/thread.h" /** * Releases lock on stdio object. diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index ea0b26640..fc8d5bef9 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -4,6 +4,10 @@ #include "libc/runtime/runtime.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" + +#define PT_OWNSTACK 1 +#define PT_MAINTHREAD 2 + #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -52,9 +56,6 @@ enum PosixThreadStatus { // - kPosixThreadZombie -> _pthread_free() will happen whenever // convenient, e.g. pthread_create() entry or atexit handler. kPosixThreadZombie, - - // special main thread - kPosixThreadMain, }; struct PosixThread { @@ -62,15 +63,17 @@ struct PosixThread { void *(*start_routine)(void *); void *arg; // start_routine's parameter void *rc; // start_routine's return value - bool ownstack; // should we free it + int flags; // see PT_* constants int tid; // clone parent tid char *altstack; // thread sigaltstack char *tls; // bottom of tls allocation struct CosmoTib *tib; // middle of tls allocation jmp_buf exiter; // for pthread_exit pthread_attr_t attr; + struct _pthread_cleanup_buffer *cleanup; }; +extern struct PosixThread _pthread_main; hidden extern pthread_spinlock_t _pthread_keys_lock; hidden extern uint64_t _pthread_key_usage[(PTHREAD_KEYS_MAX + 63) / 64]; hidden extern pthread_key_dtor _pthread_key_dtor[PTHREAD_KEYS_MAX]; @@ -79,6 +82,7 @@ hidden extern _Thread_local void *_pthread_keys[PTHREAD_KEYS_MAX]; int _pthread_reschedule(struct PosixThread *) hidden; int _pthread_setschedparam_freebsd(int, int, const struct sched_param *) hidden; void _pthread_free(struct PosixThread *) hidden; +void _pthread_cleanup(struct PosixThread *) hidden; void _pthread_ungarbage(void) hidden; void _pthread_wait(struct PosixThread *) hidden; void _pthread_zombies_add(struct PosixThread *) hidden; diff --git a/libc/thread/pthread_attr_getdetachstate.c b/libc/thread/pthread_attr_getdetachstate.c index 742d26b00..2ae2d1c0d 100644 --- a/libc/thread/pthread_attr_getdetachstate.c +++ b/libc/thread/pthread_attr_getdetachstate.c @@ -27,6 +27,6 @@ * @return 0 on success, or error on failure */ int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) { - *detachstate = attr->detachstate; + *detachstate = attr->__detachstate; return 0; } diff --git a/libc/thread/pthread_attr_getguardsize.c b/libc/thread/pthread_attr_getguardsize.c index 4d50f5648..d6467d90b 100644 --- a/libc/thread/pthread_attr_getguardsize.c +++ b/libc/thread/pthread_attr_getguardsize.c @@ -26,6 +26,6 @@ */ errno_t pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize) { - *guardsize = attr->guardsize; + *guardsize = attr->__guardsize; return 0; } diff --git a/libc/thread/pthread_attr_getinheritsched.c b/libc/thread/pthread_attr_getinheritsched.c index f8b07caa1..97e757961 100644 --- a/libc/thread/pthread_attr_getinheritsched.c +++ b/libc/thread/pthread_attr_getinheritsched.c @@ -23,6 +23,6 @@ */ int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched) { - *inheritsched = attr->inheritsched; + *inheritsched = attr->__inheritsched; return 0; } diff --git a/libc/thread/pthread_attr_getschedparam.c b/libc/thread/pthread_attr_getschedparam.c index 25e8634b6..694e9629b 100644 --- a/libc/thread/pthread_attr_getschedparam.c +++ b/libc/thread/pthread_attr_getschedparam.c @@ -23,6 +23,6 @@ */ int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param) { - *param = (struct sched_param){attr->schedparam}; + *param = (struct sched_param){attr->__schedparam}; return 0; } diff --git a/libc/thread/pthread_attr_getschedpolicy.c b/libc/thread/pthread_attr_getschedpolicy.c index 88f8e277a..b07164899 100644 --- a/libc/thread/pthread_attr_getschedpolicy.c +++ b/libc/thread/pthread_attr_getschedpolicy.c @@ -22,6 +22,6 @@ * Gets thread scheduler policy attribute */ int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy) { - *policy = attr->schedpolicy; + *policy = attr->__schedpolicy; return 0; } diff --git a/libc/thread/pthread_attr_getscope.c b/libc/thread/pthread_attr_getscope.c index 3759ae815..ed264edf6 100644 --- a/libc/thread/pthread_attr_getscope.c +++ b/libc/thread/pthread_attr_getscope.c @@ -19,6 +19,6 @@ #include "libc/thread/thread.h" int pthread_attr_getscope(const pthread_attr_t *a, int *x) { - *x = a->scope; + *x = a->__scope; return 0; } diff --git a/libc/thread/pthread_attr_getstack.c b/libc/thread/pthread_attr_getstack.c index b71808fe6..3fef5d39a 100644 --- a/libc/thread/pthread_attr_getstack.c +++ b/libc/thread/pthread_attr_getstack.c @@ -32,7 +32,7 @@ */ errno_t pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize) { - *stackaddr = attr->stackaddr; - *stacksize = attr->stacksize; + *stackaddr = attr->__stackaddr; + *stacksize = attr->__stacksize; return 0; } diff --git a/libc/thread/pthread_attr_getstacksize.c b/libc/thread/pthread_attr_getstacksize.c index 8f1d7552b..25fac4615 100644 --- a/libc/thread/pthread_attr_getstacksize.c +++ b/libc/thread/pthread_attr_getstacksize.c @@ -29,8 +29,8 @@ * @see pthread_attr_setstacksize() */ errno_t pthread_attr_getstacksize(const pthread_attr_t *a, size_t *x) { - if (a->stacksize) { - *x = a->stacksize; + if (a->__stacksize) { + *x = a->__stacksize; } else { *x = GetStackSize(); } diff --git a/libc/thread/pthread_attr_init.c b/libc/thread/pthread_attr_init.c index 14fe569aa..9ff1fca92 100644 --- a/libc/thread/pthread_attr_init.c +++ b/libc/thread/pthread_attr_init.c @@ -26,8 +26,8 @@ */ errno_t pthread_attr_init(pthread_attr_t *attr) { *attr = (pthread_attr_t){ - .stacksize = GetStackSize(), - .guardsize = PAGESIZE, + .__stacksize = GetStackSize(), + .__guardsize = PAGESIZE, }; return 0; } diff --git a/libc/thread/pthread_attr_setdetachstate.c b/libc/thread/pthread_attr_setdetachstate.c index 22e03d62c..55d9edf26 100644 --- a/libc/thread/pthread_attr_setdetachstate.c +++ b/libc/thread/pthread_attr_setdetachstate.c @@ -38,7 +38,7 @@ int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) { switch (detachstate) { case PTHREAD_CREATE_JOINABLE: case PTHREAD_CREATE_DETACHED: - attr->detachstate = detachstate; + attr->__detachstate = detachstate; return 0; default: return EINVAL; diff --git a/libc/thread/pthread_attr_setguardsize.c b/libc/thread/pthread_attr_setguardsize.c index f93cab449..d6f79b248 100644 --- a/libc/thread/pthread_attr_setguardsize.c +++ b/libc/thread/pthread_attr_setguardsize.c @@ -25,6 +25,6 @@ * @return 0 on success, or errno on error */ errno_t pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize) { - attr->guardsize = guardsize; + attr->__guardsize = guardsize; return 0; } diff --git a/libc/thread/pthread_attr_setinheritsched.c b/libc/thread/pthread_attr_setinheritsched.c index 128a30158..feacb979b 100644 --- a/libc/thread/pthread_attr_setinheritsched.c +++ b/libc/thread/pthread_attr_setinheritsched.c @@ -44,7 +44,7 @@ errno_t pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched) { switch (inheritsched) { case PTHREAD_INHERIT_SCHED: case PTHREAD_EXPLICIT_SCHED: - attr->inheritsched = inheritsched; + attr->__inheritsched = inheritsched; return 0; default: assert(!"badval"); diff --git a/libc/thread/pthread_attr_setschedparam.c b/libc/thread/pthread_attr_setschedparam.c index afcf4e381..0c09a6a26 100644 --- a/libc/thread/pthread_attr_setschedparam.c +++ b/libc/thread/pthread_attr_setschedparam.c @@ -43,6 +43,6 @@ int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param) { if (!param) return EINVAL; - attr->schedparam = param->sched_priority; + attr->__schedparam = param->sched_priority; return 0; } diff --git a/libc/thread/pthread_attr_setschedpolicy.c b/libc/thread/pthread_attr_setschedpolicy.c index dafa1f46c..294573173 100644 --- a/libc/thread/pthread_attr_setschedpolicy.c +++ b/libc/thread/pthread_attr_setschedpolicy.c @@ -42,6 +42,6 @@ * @see sched_setscheduler() */ int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) { - attr->schedpolicy = policy; + attr->__schedpolicy = policy; return 0; } diff --git a/libc/thread/pthread_attr_setscope.c b/libc/thread/pthread_attr_setscope.c index 89ad4eaa8..e4aaaf856 100644 --- a/libc/thread/pthread_attr_setscope.c +++ b/libc/thread/pthread_attr_setscope.c @@ -19,6 +19,6 @@ #include "libc/thread/thread.h" int pthread_attr_setscope(pthread_attr_t *a, int x) { - a->scope = x; + a->__scope = x; return 0; } diff --git a/libc/thread/pthread_attr_setstack.c b/libc/thread/pthread_attr_setstack.c index acb7d1e5f..39d589bd7 100644 --- a/libc/thread/pthread_attr_setstack.c +++ b/libc/thread/pthread_attr_setstack.c @@ -65,15 +65,15 @@ errno_t pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize) { if (!stackaddr) { - attr->stackaddr = 0; - attr->stacksize = 0; + attr->__stackaddr = 0; + attr->__stacksize = 0; return 0; } if (stacksize < PTHREAD_STACK_MIN || (IsAsan() && !__asan_is_valid(stackaddr, stacksize))) { return EINVAL; } - attr->stackaddr = stackaddr; - attr->stacksize = stacksize; + attr->__stackaddr = stackaddr; + attr->__stacksize = stacksize; return 0; } diff --git a/libc/thread/pthread_attr_setstacksize.c b/libc/thread/pthread_attr_setstacksize.c index 327e56f77..2fc8c0642 100644 --- a/libc/thread/pthread_attr_setstacksize.c +++ b/libc/thread/pthread_attr_setstacksize.c @@ -28,6 +28,6 @@ */ errno_t pthread_attr_setstacksize(pthread_attr_t *a, size_t stacksize) { if (stacksize < PTHREAD_STACK_MIN) return EINVAL; - a->stacksize = stacksize; + a->__stacksize = stacksize; return 0; } diff --git a/libc/thread/pthread_cleanup.c b/libc/thread/pthread_cleanup.c new file mode 100644 index 000000000..b756176a6 --- /dev/null +++ b/libc/thread/pthread_cleanup.c @@ -0,0 +1,38 @@ +/*-*- 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/intrin/atomic.h" +#include "libc/intrin/weaken.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +void _pthread_cleanup(struct PosixThread *pt) { + _pthread_ungarbage(); + if (_weaken(_pthread_key_destruct)) { + _weaken(_pthread_key_destruct)(0); + } + if (atomic_load_explicit(&pt->status, memory_order_acquire) == + kPosixThreadDetached) { + atomic_store_explicit(&pt->status, kPosixThreadZombie, + memory_order_release); + } else { + atomic_store_explicit(&pt->status, kPosixThreadTerminated, + memory_order_release); + } +} diff --git a/libc/thread/pthread_cleanup_pop.c b/libc/thread/pthread_cleanup_pop.c new file mode 100644 index 000000000..b9efb902e --- /dev/null +++ b/libc/thread/pthread_cleanup_pop.c @@ -0,0 +1,30 @@ +/*-*- 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +void _pthread_cleanup_pop(struct _pthread_cleanup_buffer *cb, int execute) { + struct PosixThread *pt = (struct PosixThread *)pthread_self(); + _npassert(cb == pt->cleanup); + pt->cleanup = cb->__prev; + if (execute) { + cb->__routine(cb->__arg); + } +} diff --git a/libc/thread/pthread_cleanup_push.c b/libc/thread/pthread_cleanup_push.c new file mode 100644 index 000000000..125d30c18 --- /dev/null +++ b/libc/thread/pthread_cleanup_push.c @@ -0,0 +1,29 @@ +/*-*- 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +void _pthread_cleanup_push(struct _pthread_cleanup_buffer *cb, + void (*routine)(void *), void *arg) { + struct PosixThread *pt = (struct PosixThread *)pthread_self(); + cb->__routine = routine; + cb->__arg = arg; + cb->__prev = pt->cleanup; + pt->cleanup = cb; +} diff --git a/libc/thread/pthread_create.c b/libc/thread/pthread_create.c index e32ae5b96..920c1a1da 100644 --- a/libc/thread/pthread_create.c +++ b/libc/thread/pthread_create.c @@ -57,11 +57,12 @@ void _pthread_wait(struct PosixThread *pt) { } void _pthread_free(struct PosixThread *pt) { + if (pt->flags & PT_MAINTHREAD) return; free(pt->tls); - if (pt->ownstack && // - pt->attr.stackaddr && // - pt->attr.stackaddr != MAP_FAILED) { - if (munmap(pt->attr.stackaddr, pt->attr.stacksize)) { + if ((pt->flags & PT_OWNSTACK) && // + pt->attr.__stackaddr && // + pt->attr.__stackaddr != MAP_FAILED) { + if (munmap(pt->attr.__stackaddr, pt->attr.__stacksize)) { notpossible; } } @@ -83,25 +84,19 @@ static int PosixThread(void *arg, int tid) { notpossible; } } - if (pt->attr.inheritsched == PTHREAD_EXPLICIT_SCHED) { + if (pt->attr.__inheritsched == PTHREAD_EXPLICIT_SCHED) { _pthread_reschedule(pt); } + // set long jump handler so pthread_exit can bring control back here if (!setjmp(pt->exiter)) { __get_tls()->tib_pthread = (pthread_t)pt; pt->rc = pt->start_routine(pt->arg); + // ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup + _npassert(!pt->cleanup); } - if (_weaken(_pthread_key_destruct)) { - _weaken(_pthread_key_destruct)(0); - } - _pthread_ungarbage(); - if (atomic_load_explicit(&pt->status, memory_order_acquire) == - kPosixThreadDetached) { - atomic_store_explicit(&pt->status, kPosixThreadZombie, - memory_order_release); - } else { - atomic_store_explicit(&pt->status, kPosixThreadTerminated, - memory_order_release); - } + // run garbage collector, call key destructors, and set change state + _pthread_cleanup(pt); + // return to clone polyfill which clears tid, wakes futex, and exits return 0; } @@ -113,8 +108,8 @@ static int FixupCustomStackOnOpenbsd(pthread_attr_t *attr) { size_t n; int e, rc; uintptr_t x, y; - n = attr->stacksize; - x = (uintptr_t)attr->stackaddr; + n = attr->__stacksize; + x = (uintptr_t)attr->__stackaddr; y = ROUNDUP(x, PAGESIZE); n -= y - x; n = ROUNDDOWN(n, PAGESIZE); @@ -122,8 +117,8 @@ static int FixupCustomStackOnOpenbsd(pthread_attr_t *attr) { if (__sys_mmap((void *)y, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, -1, 0, 0) == (void *)y) { - attr->stackaddr = (void *)y; - attr->stacksize = n; + attr->__stackaddr = (void *)y; + attr->__stacksize = n; return 0; } else { rc = errno; @@ -216,7 +211,7 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, } // setup stack - if (pt->attr.stackaddr) { + if (pt->attr.__stackaddr) { // caller supplied their own stack // assume they know what they're doing as much as possible if (IsOpenbsd()) { @@ -229,37 +224,40 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, // cosmo is managing the stack // 1. in mono repo optimize for tiniest stack possible // 2. in public world optimize to *work* regardless of memory - pt->ownstack = true; - pt->attr.stacksize = MAX(pt->attr.stacksize, GetStackSize()); - pt->attr.stacksize = _roundup2pow(pt->attr.stacksize); - pt->attr.guardsize = ROUNDUP(pt->attr.guardsize, PAGESIZE); - if (pt->attr.guardsize + PAGESIZE >= pt->attr.stacksize) { + pt->flags = PT_OWNSTACK; + pt->attr.__stacksize = MAX(pt->attr.__stacksize, GetStackSize()); + pt->attr.__stacksize = _roundup2pow(pt->attr.__stacksize); + pt->attr.__guardsize = ROUNDUP(pt->attr.__guardsize, PAGESIZE); + if (pt->attr.__guardsize + PAGESIZE >= pt->attr.__stacksize) { _pthread_free(pt); return EINVAL; } - if (pt->attr.guardsize == PAGESIZE) { + if (pt->attr.__guardsize == PAGESIZE) { // user is wisely using smaller stacks with default guard size - pt->attr.stackaddr = mmap(0, pt->attr.stacksize, PROT_READ | PROT_WRITE, - MAP_STACK | MAP_ANONYMOUS, -1, 0); + pt->attr.__stackaddr = + mmap(0, pt->attr.__stacksize, PROT_READ | PROT_WRITE, + MAP_STACK | MAP_ANONYMOUS, -1, 0); } else { // user is tuning things, performance may suffer - pt->attr.stackaddr = mmap(0, pt->attr.stacksize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (pt->attr.stackaddr != MAP_FAILED) { + pt->attr.__stackaddr = + mmap(0, pt->attr.__stacksize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pt->attr.__stackaddr != MAP_FAILED) { if (IsOpenbsd() && __sys_mmap( - pt->attr.stackaddr, pt->attr.stacksize, PROT_READ | PROT_WRITE, + pt->attr.__stackaddr, pt->attr.__stacksize, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, - -1, 0, 0) != pt->attr.stackaddr) { + -1, 0, 0) != pt->attr.__stackaddr) { notpossible; } - if (pt->attr.guardsize && !IsWindows() && - mprotect(pt->attr.stackaddr, pt->attr.guardsize, PROT_NONE)) { + if (pt->attr.__guardsize && !IsWindows() && + mprotect(pt->attr.__stackaddr, pt->attr.__guardsize, PROT_NONE)) { notpossible; } } } - if (pt->attr.stackaddr == MAP_FAILED) { + if (pt->attr.__stackaddr == MAP_FAILED) { rc = errno; _pthread_free(pt); errno = e; @@ -269,8 +267,9 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, return EAGAIN; } } - if (IsAsan() && pt->attr.guardsize) { - __asan_poison(pt->attr.stackaddr, pt->attr.guardsize, kAsanStackOverflow); + if (IsAsan() && pt->attr.__guardsize) { + __asan_poison(pt->attr.__stackaddr, pt->attr.__guardsize, + kAsanStackOverflow); } } @@ -280,7 +279,7 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, } // set initial status - switch (pt->attr.detachstate) { + switch (pt->attr.__detachstate) { case PTHREAD_CREATE_JOINABLE: pt->status = kPosixThreadJoinable; break; @@ -294,8 +293,8 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr, } // launch PosixThread(pt) in new thread - if (clone(PosixThread, pt->attr.stackaddr, - pt->attr.stacksize - (IsOpenbsd() ? 16 : 0), + if (clone(PosixThread, pt->attr.__stackaddr, + pt->attr.__stacksize - (IsOpenbsd() ? 16 : 0), CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID, diff --git a/libc/thread/pthread_exit.c b/libc/thread/pthread_exit.c index 14189ff35..93529da7f 100644 --- a/libc/thread/pthread_exit.c +++ b/libc/thread/pthread_exit.c @@ -16,19 +16,45 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/limits.h" #include "libc/mem/gc.h" +#include "libc/runtime/runtime.h" #include "libc/thread/posixthread.internal.h" -#include "libc/thread/spawn.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" +#include "third_party/nsync/futex.internal.h" + +STATIC_YOINK("_pthread_main"); /** * Terminates current POSIX thread. * - * If this function is called from the main thread, or a thread created - * with clone() or _spawn(), then this function is the same as _Exit1() - * in which case `rc` is coerced to a `uint8_t` exit status, which will - * only be reported to the parent process on Linux, FreeBSD and Windows + * For example, a thread could terminate early as follows: + * + * pthread_exit((void *)123); + * + * The result value could then be obtained when joining the thread: + * + * void *rc; + * pthread_join(id, &rc); + * assert((intptr_t)rc == 123); + * + * Under normal circumstances a thread can exit by simply returning from + * the callback function that was supplied to pthread_create(). This may + * be used if the thread wishes to exit at any other point in the thread + * lifecycle, in which case this function is responsible for ensuring we + * invoke _gc(), _defer(), and pthread_cleanup_push() callbacks, as well + * as pthread_key_create() destructors. + * + * If the current thread is an orphaned thread, or is the main thread + * when no other threads were created, then this will terminated your + * process with an exit code of zero. It's not possible to supply a + * non-zero exit status to wait4() via this function. + * + * Once a thread has exited, access to its stack memory is undefined. + * The behavior of calling pthread_exit() from cleanup handlers and key + * destructors is also undefined. * * @param rc is reported later to pthread_join() * @threadsafe @@ -36,11 +62,32 @@ */ wontreturn void pthread_exit(void *rc) { struct PosixThread *pt; - pt = (struct PosixThread *)pthread_self(); - if (pt->status != kPosixThreadMain) { - pt->rc = rc; - _gclongjmp(pt->exiter, 1); + struct _pthread_cleanup_buffer *cb; + pt = (struct PosixThread *)__get_tls()->tib_pthread; + pt->rc = rc; + // the memory of pthread cleanup objects lives on the stack + // so we need to harvest them before calling longjmp() + while ((cb = pt->cleanup)) { + pt->cleanup = cb->__prev; + cb->__routine(cb->__arg); + } + if (~pt->flags & PT_MAINTHREAD) { + // this thread was created by pthread_create() + // garbage collector memory exists on a shadow stack. we don't need + // to use _gclongjmp() since _pthread_ungarbage() will collect them + // at the setjmp() site. + longjmp(pt->exiter, 1); } else { - _Exit1((int)(intptr_t)rc); + // this is the main thread + // release as much resources and possible and mark it terminated + _pthread_cleanup(pt); + // it's kind of irregular for a child thread to join the main thread + // so we don't bother freeing the main thread's stack since it makes + // this implementation so much simpler for example we want't to call + // set_tid_address() upon every program startup which isn't possible + // on non-linux platforms anyway. + __get_tls()->tib_tid = 0; + nsync_futex_wake_((int *)&__get_tls()->tib_tid, INT_MAX, !IsWindows()); + _Exit1(0); } } diff --git a/libc/thread/pthread_getattr_np.c b/libc/thread/pthread_getattr_np.c index 479f4371c..49e730696 100644 --- a/libc/thread/pthread_getattr_np.c +++ b/libc/thread/pthread_getattr_np.c @@ -16,12 +16,51 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/atomic.h" #include "libc/str/str.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" +/** + * Gets thread attributes. + * + * These attributes are copied from the ones supplied when + * pthread_create() was called. However this function supplies + * additional runtime information too: + * + * 1. The detached state. You can use pthread_attr_getdetachstate() on + * the `attr` result to see, for example, if a thread detached itself + * or some other thread detached it, after it was spawned. + * + * 2. The thread's stack. You can use pthread_attr_getstack() to see the + * address and size of the stack that was allocated by cosmo for your + * thread. This is useful for knowing where the stack is. It can also + * be useful If you explicitly configured a stack too, since we might + * have needed to slightly tune the address and size to meet platform + * requirements. This function returns information that reflects that + * + * 3. You can view changes pthread_create() may have made to the stack + * guard size by calling pthread_attr_getguardsize() on `attr` + * + * @param attr is output argument that receives attributes, which should + * find its way to pthread_attr_destroy() when it's done being used + * @return 0 on success, or errno on error + * @raise ENOMEM is listed as a possible result by LSB 5.0 + */ int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) { struct PosixThread *pt = (struct PosixThread *)thread; memcpy(attr, &pt->attr, sizeof(pt->attr)); + switch (atomic_load_explicit(&pt->status, memory_order_relaxed)) { + case kPosixThreadJoinable: + case kPosixThreadTerminated: + attr->__detachstate = PTHREAD_CREATE_JOINABLE; + break; + case kPosixThreadDetached: + case kPosixThreadZombie: + attr->__detachstate = PTHREAD_CREATE_DETACHED; + break; + default: + unreachable; + } return 0; } diff --git a/libc/thread/pthread_getschedparam.c b/libc/thread/pthread_getschedparam.c index f382a59dc..bd3093aa1 100644 --- a/libc/thread/pthread_getschedparam.c +++ b/libc/thread/pthread_getschedparam.c @@ -25,7 +25,7 @@ int pthread_getschedparam(pthread_t thread, int *policy, struct sched_param *param) { struct PosixThread *pt = (struct PosixThread *)thread; - *policy = pt->attr.schedpolicy; - *param = (struct sched_param){pt->attr.schedparam}; + *policy = pt->attr.__schedpolicy; + *param = (struct sched_param){pt->attr.__schedparam}; return 0; } diff --git a/libc/thread/pthread_join.c b/libc/thread/pthread_join.c index 88bf72559..c5b7389a2 100644 --- a/libc/thread/pthread_join.c +++ b/libc/thread/pthread_join.c @@ -24,14 +24,16 @@ /** * Waits for thread to terminate. * + * @param value_ptr if non-null will receive pthread_exit() argument * @return 0 on success, or errno with error - * @raise EDEADLK if thread is detached + * @raise EDEADLK if `thread` is the current thread + * @raise EINVAL if `thread` is detached * @returnserrno * @threadsafe */ int pthread_join(pthread_t thread, void **value_ptr) { struct PosixThread *pt; - if (thread == pthread_self()) { + if (thread == __get_tls()->tib_pthread) { return EDEADLK; } if (!(pt = (struct PosixThread *)thread) || // diff --git a/libc/thread/pthread_main.c b/libc/thread/pthread_main.c new file mode 100644 index 000000000..f3a2115ff --- /dev/null +++ b/libc/thread/pthread_main.c @@ -0,0 +1,31 @@ +/*-*- 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. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/thread/posixthread.internal.h" + +// it's only be possible for this memory to be accessed when the user +// has linked either pthread_self() or pthread_exit() which yoink it. +struct PosixThread _pthread_main; + +__attribute__((__constructor__)) static void pthread_self_init(void) { + _pthread_main.tid = gettid(); + _pthread_main.tib = __get_tls(); + _pthread_main.flags = PT_MAINTHREAD; + __get_tls()->tib_pthread = (pthread_t)&_pthread_main; +} diff --git a/libc/thread/pthread_reschedule.c b/libc/thread/pthread_reschedule.c index c3ad753aa..1319a7dc2 100644 --- a/libc/thread/pthread_reschedule.c +++ b/libc/thread/pthread_reschedule.c @@ -25,13 +25,14 @@ int _pthread_reschedule(struct PosixThread *pt) { int rc, e = errno; - struct sched_param param = {pt->attr.schedparam}; + int policy = pt->attr.__schedpolicy; + struct sched_param param = {pt->attr.__schedparam}; if (IsNetbsd()) { - rc = sys_sched_setparam_netbsd(0, pt->tid, pt->attr.schedpolicy, ¶m); + rc = sys_sched_setparam_netbsd(0, pt->tid, policy, ¶m); } else if (IsLinux()) { - rc = sys_sched_setscheduler(pt->tid, pt->attr.schedpolicy, ¶m); + rc = sys_sched_setscheduler(pt->tid, policy, ¶m); } else if (IsFreebsd()) { - rc = _pthread_setschedparam_freebsd(pt->tid, pt->attr.schedpolicy, ¶m); + rc = _pthread_setschedparam_freebsd(pt->tid, policy, ¶m); } else { rc = enosys(); } diff --git a/libc/thread/pthread_self.c b/libc/thread/pthread_self.c index 2bc6fee4d..b228c63b0 100644 --- a/libc/thread/pthread_self.c +++ b/libc/thread/pthread_self.c @@ -16,21 +16,14 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/calls.h" -#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" +STATIC_YOINK("_pthread_main"); + /** * Returns current POSIX thread. */ pthread_t pthread_self(void) { return __get_tls()->tib_pthread; } - -static struct PosixThread pthread_main; -__attribute__((__constructor__)) static void pthread_self_init(void) { - pthread_main.tid = gettid(); - pthread_main.status = kPosixThreadMain; - __get_tls()->tib_pthread = (pthread_t)&pthread_main; -} diff --git a/libc/thread/pthread_setschedparam.c b/libc/thread/pthread_setschedparam.c index e0d2fce57..7126ba710 100644 --- a/libc/thread/pthread_setschedparam.c +++ b/libc/thread/pthread_setschedparam.c @@ -45,7 +45,7 @@ int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param) { struct PosixThread *pt = (struct PosixThread *)thread; if (!param) return EINVAL; - pt->attr.schedpolicy = policy; - pt->attr.schedparam = param->sched_priority; + pt->attr.__schedpolicy = policy; + pt->attr.__schedparam = param->sched_priority; return _pthread_reschedule(pt); } diff --git a/libc/thread/pthread_ungarbage.c b/libc/thread/pthread_ungarbage.c index 0a33e7011..50511da73 100644 --- a/libc/thread/pthread_ungarbage.c +++ b/libc/thread/pthread_ungarbage.c @@ -16,17 +16,17 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/gc.internal.h" #include "libc/thread/tls.h" void _pthread_ungarbage(void) { + int i; struct Garbages *g; if ((g = __get_tls()->tib_garbages)) { - // _pthread_exit() uses _gclongjmp() so if this assertion fails, - // then the likely cause is the thread used gc() with longjmp(). - assert(!g->i); + for (i = g->i; i--;) { + ((void (*)(intptr_t))g->p[i].fn)(g->p[i].arg); + } free(g->p); free(g); } diff --git a/libc/thread/thread.h b/libc/thread/thread.h index 119f7f733..6fc8af956 100644 --- a/libc/thread/thread.h +++ b/libc/thread/thread.h @@ -77,16 +77,23 @@ typedef struct pthread_barrier_s { } pthread_barrier_t; typedef struct pthread_attr_s { - char detachstate; - char inheritsched; - int schedparam; - int schedpolicy; - int scope; - unsigned guardsize; - unsigned stacksize; - char *stackaddr; + char __detachstate; + char __inheritsched; + int __schedparam; + int __schedpolicy; + int __scope; + unsigned __guardsize; + unsigned __stacksize; + char *__stackaddr; } pthread_attr_t; +struct _pthread_cleanup_buffer { + void (*__routine)(void *); + void *__arg; + int __canceltype; + struct _pthread_cleanup_buffer *__prev; +}; + int pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *), void *); @@ -168,6 +175,18 @@ int pthread_barrier_wait(pthread_barrier_t *); int pthread_barrier_destroy(pthread_barrier_t *); int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *, unsigned); +void _pthread_cleanup_pop(struct _pthread_cleanup_buffer *, int); +void _pthread_cleanup_push(struct _pthread_cleanup_buffer *, void (*)(void *), + void *); + +#define pthread_cleanup_push(routine, arg) \ + { \ + struct _pthread_cleanup_buffer _buffer; \ + _pthread_cleanup_push(&_buffer, (routine), (arg)); + +#define pthread_cleanup_pop(execute) \ + _pthread_cleanup_pop(&_buffer, (execute)); \ + } #define pthread_spin_init(pSpin, multiprocess) ((pSpin)->_lock = 0, 0) #define pthread_spin_destroy(pSpin) ((pSpin)->_lock = -1, 0) diff --git a/libc/thread/tls.h b/libc/thread/tls.h index bce67c812..49edd8c50 100644 --- a/libc/thread/tls.h +++ b/libc/thread/tls.h @@ -1,6 +1,5 @@ #ifndef COSMOPOLITAN_LIBC_THREAD_TLS_H_ #define COSMOPOLITAN_LIBC_THREAD_TLS_H_ -#include "libc/thread/thread.h" #define TLS_ALIGNMENT 64 @@ -33,7 +32,7 @@ struct CosmoTib { void *tib_reserved5; void *tib_reserved6; void *tib_reserved7; - void *tib_keys[PTHREAD_KEYS_MAX]; + void *tib_keys[128]; }; extern int __threaded; diff --git a/libc/thread/wait0.c b/libc/thread/wait0.c index f5dfb0c23..fcb88bdd0 100644 --- a/libc/thread/wait0.c +++ b/libc/thread/wait0.c @@ -16,71 +16,10 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/atomic.h" -#include "libc/calls/calls.h" -#include "libc/calls/struct/timespec.h" -#include "libc/calls/struct/timespec.internal.h" #include "libc/dce.h" -#include "libc/errno.h" #include "libc/intrin/atomic.h" -#include "libc/intrin/describeflags.internal.h" -#include "libc/intrin/kprintf.h" -#include "libc/intrin/strace.internal.h" -#include "libc/nt/runtime.h" -#include "libc/nt/synchronization.h" -#include "libc/sysv/consts/futex.h" -#include "libc/thread/freebsd.internal.h" #include "libc/thread/wait0.internal.h" - -int _futex(atomic_int *, int, int, const struct timespec *); - -static int _wait0_sleep(struct timespec *ts) { - int rc, e = errno; - if ((rc = nanosleep(ts, 0))) { - _npassert(errno == EINTR); - errno = e; - } - return rc; -} - -static void _wait0_poll(struct timespec *ts) { - if (ts->tv_nsec < 1000) { - // prefer sched_yield() for small time intervals because nanosleep() - // will ceiling round to 1ms on the new technology. - sched_yield(); - ts->tv_nsec <<= 1; - } else if (!_wait0_sleep(ts)) { - if (ts->tv_nsec < 100 * 1000 * 1000) { - ts->tv_nsec <<= 1; - } - } -} - -static void _wait0_futex(const atomic_int *a, int e) { - int rc, op; - op = FUTEX_WAIT; // we need a shared mutex - if (IsWindows()) { - if (WaitOnAddress(a, &e, sizeof(*a), -1)) { - rc = 0; - } else { - rc = -GetLastError(); - } - } else if (IsFreebsd()) { - rc = sys_umtx_op(a, UMTX_OP_WAIT_UINT, e, 0, 0); - } else { - rc = _futex(a, op, e, 0); - if (IsOpenbsd() && rc > 0) { - rc = -rc; - } - } - STRACE("futex(%t, %s, %d, %s) → %s", a, DescribeFutexOp(op), e, "NULL", - DescribeErrnoResult(rc)); - _npassert(rc == 0 || // - rc == -EINTR || // - rc == -ETIMEDOUT || // - rc == -EWOULDBLOCK); -} +#include "third_party/nsync/futex.internal.h" /** * Blocks until memory location becomes zero. @@ -88,20 +27,12 @@ static void _wait0_futex(const atomic_int *a, int e) { * This is intended to be used on the child thread id, which is updated * by the clone() system call when a thread terminates. We need this in * order to know when it's safe to free a thread's stack. This function - * uses futexes on Linux, OpenBSD, and Windows. On other platforms this - * uses polling with exponential backoff. + * uses futexes on Linux, FreeBSD, OpenBSD, and Windows. On other + * platforms this uses polling with exponential backoff. */ void _wait0(const atomic_int *ctid) { int x; - struct timespec ts = {0, 1}; - while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { - if (IsLinux() || IsOpenbsd() || IsWindows()) { - _wait0_futex(ctid, x); - } else { - _wait0_poll(&ts); - } - } - if (IsOpenbsd()) { - sched_yield(); // TODO(jart): whhhy? + while ((x = atomic_load_explicit(ctid, memory_order_relaxed))) { + nsync_futex_wait_((int *)ctid, x, !IsWindows(), 0); } } diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index c8234edbe..18524a4cd 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -1714,7 +1714,7 @@ OnError: } int main(int argc, char *argv[]) { - ShowCrashReports(); + // ShowCrashReports(); // we don't have proper futexes on these platforms // so choose a smaller number of workers diff --git a/test/libc/calls/pledge_test.c b/test/libc/calls/pledge_test.c index 31ddbc1f1..81f0bef3c 100644 --- a/test/libc/calls/pledge_test.c +++ b/test/libc/calls/pledge_test.c @@ -58,6 +58,7 @@ #include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" #include "libc/thread/spawn.h" +#include "libc/thread/thread.h" #include "libc/time/time.h" #include "libc/x/x.h" diff --git a/test/libc/thread/pthread_create_test.c b/test/libc/thread/pthread_create_test.c index fbc67eb5c..20a26d229 100644 --- a/test/libc/thread/pthread_create_test.c +++ b/test/libc/thread/pthread_create_test.c @@ -16,13 +16,14 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/atomic.h" #include "libc/calls/calls.h" #include "libc/calls/struct/sched_param.h" #include "libc/calls/struct/sigaction.h" #include "libc/dce.h" #include "libc/errno.h" -#include "libc/intrin/kprintf.h" #include "libc/macros.internal.h" +#include "libc/mem/gc.h" #include "libc/mem/mem.h" #include "libc/nexgen32e/nexgen32e.h" #include "libc/runtime/runtime.h" @@ -48,7 +49,6 @@ void SetUp(void) { void TriggerSignal(void) { sched_yield(); - /* kprintf("raising at %p\n", __builtin_frame_address(0)); */ raise(SIGUSR1); sched_yield(); } @@ -183,19 +183,90 @@ TEST(pthread_detach, testCustomStack_withReallySmallSize) { free(stk); } -TEST(pthread_exit, mainThreadWorks) { - // _Exit1() can't set process exit code on XNU/NetBSD/OpenBSD. - if (IsLinux() || IsFreebsd() || IsWindows()) { - SPAWN(fork); - pthread_exit((void *)2); - EXITS(2); - } else { - SPAWN(fork); - pthread_exit((void *)0); - EXITS(0); - } +void *JoinMainWorker(void *arg) { + void *rc; + pthread_t main_thread = (pthread_t)arg; + _gc(malloc(32)); + _gc(malloc(32)); + ASSERT_EQ(0, pthread_join(main_thread, &rc)); + ASSERT_EQ(123, (intptr_t)rc); + return 0; } +TEST(pthread_join, mainThread) { + pthread_t id; + _gc(malloc(32)); + _gc(malloc(32)); + SPAWN(fork); + ASSERT_EQ(0, pthread_create(&id, 0, JoinMainWorker, (void *)pthread_self())); + pthread_exit((void *)123); + EXITS(0); +} + +TEST(pthread_join, mainThreadDelayed) { + pthread_t id; + _gc(malloc(32)); + _gc(malloc(32)); + SPAWN(fork); + ASSERT_EQ(0, pthread_create(&id, 0, JoinMainWorker, (void *)pthread_self())); + usleep(10000); + pthread_exit((void *)123); + EXITS(0); +} + +TEST(pthread_exit, fromMainThread_whenNoThreadsWereCreated) { + SPAWN(fork); + pthread_exit((void *)123); + EXITS(0); +} + +atomic_bool g_cleanup1; +atomic_bool g_cleanup2; + +void OnCleanup(void *arg) { + *(atomic_bool *)arg = true; +} + +void *CleanupExit(void *arg) { + pthread_cleanup_push(OnCleanup, &g_cleanup1); + pthread_cleanup_push(OnCleanup, &g_cleanup2); + pthread_cleanup_pop(false); + pthread_exit(0); + pthread_cleanup_pop(false); + return 0; +} + +TEST(pthread_cleanup, pthread_exit_alwaysCallsCallback) { + pthread_t id; + g_cleanup1 = false; + g_cleanup2 = false; + ASSERT_EQ(0, pthread_create(&id, 0, CleanupExit, 0)); + ASSERT_EQ(0, pthread_join(id, 0)); + ASSERT_TRUE(g_cleanup1); + ASSERT_FALSE(g_cleanup2); +} + +void *CleanupNormal(void *arg) { + pthread_cleanup_push(OnCleanup, &g_cleanup1); + pthread_cleanup_push(OnCleanup, &g_cleanup2); + pthread_cleanup_pop(true); + pthread_cleanup_pop(true); + return 0; +} + +TEST(pthread_cleanup, pthread_normal) { + pthread_t id; + g_cleanup1 = false; + g_cleanup2 = false; + ASSERT_EQ(0, pthread_create(&id, 0, CleanupNormal, 0)); + ASSERT_EQ(0, pthread_join(id, 0)); + ASSERT_TRUE(g_cleanup1); + ASSERT_TRUE(g_cleanup2); +} + +//////////////////////////////////////////////////////////////////////////////// +// BENCHMARKS + static void CreateJoin(void) { pthread_t id; ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0));