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()
This commit is contained in:
Justine Tunney 2022-10-08 23:54:05 -07:00
parent 38df0a4186
commit 4a6fd3d910
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
52 changed files with 586 additions and 241 deletions

View file

@ -2,6 +2,7 @@
#define COSMOPOLITAN_LIBC_ASSERT_H_ #define COSMOPOLITAN_LIBC_ASSERT_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
#include "libc/intrin/kprintf.h"
extern bool __assert_disable; extern bool __assert_disable;
void __assert_fail(const char *, const char *, int) hidden relegated; 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) } while (0)
#define _npassert(x) \ #define _npassert(x) \
do { \ do { \
if (__builtin_expect(!(x), 0)) { \ if (__builtin_expect(!(x), 0)) { \
notpossible; \ kprintf("%s:%d: oh no!\n", __FILE__, __LINE__); \
} \ notpossible; \
} \
} while (0) } while (0)
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_

View file

@ -11,6 +11,9 @@ int sigaddset(sigset_t *, int) paramsnonnull();
int sigdelset(sigset_t *, int) paramsnonnull(); int sigdelset(sigset_t *, int) paramsnonnull();
int sigemptyset(sigset_t *) paramsnonnull(); int sigemptyset(sigset_t *) paramsnonnull();
int sigfillset(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 sigismember(const sigset_t *, int) paramsnonnull() nosideeffect;
int sigprocmask(int, const sigset_t *, sigset_t *); int sigprocmask(int, const sigset_t *, sigset_t *);
int sigsuspend(const sigset_t *); int sigsuspend(const sigset_t *);

View file

@ -16,19 +16,24 @@
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/intrin/strace.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/asmflag.h" #include "libc/intrin/asmflag.h"
#include "libc/intrin/promises.internal.h" #include "libc/intrin/promises.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/thread.h" #include "libc/nt/thread.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/sysv/consts/nr.h" #include "libc/sysv/consts/nr.h"
#include "libc/thread/tls.h"
__msabi extern typeof(ExitThread) *const __imp_ExitThread; __msabi extern typeof(ExitThread) *const __imp_ExitThread;
/** /**
* Terminates thread with raw system call. * 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 * If this is the main thread, or an orphaned child thread, then this
* function is equivalent to exiting the process; however, `rc` shall * function is equivalent to exiting the process; however, `rc` shall
* only be reported to the parent process on Linux, FreeBSD & Windows * only be reported to the parent process on Linux, FreeBSD & Windows

36
libc/intrin/sigandset.c Normal file
View file

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

View file

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

36
libc/intrin/sigorset.c Normal file
View file

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

View file

@ -16,12 +16,12 @@
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/intrin/safemacros.internal.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/intrin/strace.internal.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/fmt/fmt.h" #include "libc/fmt/fmt.h"
#include "libc/intrin/kprintf.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/check.h"
#include "libc/log/color.internal.h" #include "libc/log/color.internal.h"
#include "libc/log/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", __argv[i]);
} }
kprintf("%s\n", RESET); kprintf("%s\n", RESET);
if (!IsTiny() && e == ENOMEM) {
__print_maps();
}
__die(); __die();
unreachable; unreachable;
} }

View file

@ -26,6 +26,7 @@
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/runtime/symbols.internal.h" #include "libc/runtime/symbols.internal.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/thread/thread.h"
#include "third_party/dlmalloc/dlmalloc.h" #include "third_party/dlmalloc/dlmalloc.h"
/** /**

View file

@ -223,10 +223,10 @@ relegated void ShowCrashReport(int err, int sig, struct siginfo *si,
host, getpid(), gettid(), program_invocation_name, names.sysname, host, getpid(), gettid(), program_invocation_name, names.sysname,
names.version, names.nodename, names.release); names.version, names.nodename, names.release);
if (ctx) { if (ctx) {
kprintf("\n");
ShowFunctionCalls(ctx);
ShowGeneralRegisters(ctx); ShowGeneralRegisters(ctx);
ShowSseRegisters(ctx); ShowSseRegisters(ctx);
kprintf("\n");
ShowFunctionCalls(ctx);
} }
kprintf("\n"); kprintf("\n");
__print_maps(); __print_maps();

View file

@ -57,4 +57,5 @@ __gc: mov %fs:0,%rcx # __get_tls()
leave leave
ret ret
9: ud2 9: ud2
nop
.endfn __gc,globl,hidden .endfn __gc,globl,hidden

View file

@ -240,12 +240,14 @@ static wontreturn void FreebsdThreadMain(void *p) {
// we no longer use the stack after this point // we no longer use the stack after this point
// void thr_exit(%rdi = long *state); // void thr_exit(%rdi = long *state);
asm volatile("movl\t$0,%0\n\t" // *wt->ztid = 0 asm volatile("movl\t$0,%0\n\t" // *wt->ztid = 0
"syscall\n\t" // _umtx_op() "syscall\n\t" // _umtx_op(wt->ztid, WAKE, INT_MAX)
"movl\t$431,%%eax\n\t" // thr_exit() "movl\t$431,%%eax\n\t" // thr_exit(long *nonzeroes_and_wake)
"xor\t%%edi,%%edi\n\t" "xor\t%%edi,%%edi\n\t" // sad we can't use this free futex op
"syscall" "syscall\n\t" // exit1() fails if thread is orphaned
"movl\t$1,%%eax\n\t" // exit()
"syscall" //
: "=m"(*wt->ztid) : "=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"); : "rcx", "r8", "r9", "r10", "r11", "memory");
notpossible; notpossible;
} }
@ -289,15 +291,6 @@ static int CloneFreebsd(int (*func)(void *, int), char *stk, size_t stksz,
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// OPEN BESIYATA DISHMAYA // 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: // we can't use address sanitizer because:
// 1. __asan_handle_no_return wipes stack [todo?] // 1. __asan_handle_no_return wipes stack [todo?]
noasan static wontreturn void OpenbsdThreadMain(void *p) { noasan static wontreturn void OpenbsdThreadMain(void *p) {
@ -305,21 +298,15 @@ noasan static wontreturn void OpenbsdThreadMain(void *p) {
*wt->ptid = wt->tid; *wt->ptid = wt->tid;
*wt->ctid = wt->tid; *wt->ctid = wt->tid;
wt->func(wt->arg, wt->tid); wt->func(wt->arg, wt->tid);
// we no longer use the stack after this point. however openbsd asm volatile("mov\t%2,%%rsp\n\t" // so syscall can validate stack exists
// validates the rsp register too so a race condition can still "movl\t$0,(%%rdi)\n\t" // *wt->ztid = 0 (old stack now free'd)
// happen if the parent tries to free the stack. we'll solve it "syscall\n\t" // futex(int*, op, val) will wake wait0
// by simply changing rsp back to the old value before exiting! "xor\t%%edi,%%edi\n\t" // so kernel doesn't write to old stack
// although ideally there should be a better solution. "mov\t$302,%%eax\n\t" // __threxit(int *notdead) doesn't wake
//
// 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()
"syscall" "syscall"
: "=m"(*wt->ztid) : "=m"(*wt->ztid)
: "a"(83), "m"(oldrsp), "D"(wt->ztid), "S"(FUTEX_WAKE), : "a"(83), "m"(__oldstack), "D"(wt->ztid),
"d"(INT_MAX) "S"(2 /* FUTEX_WAKE */), "d"(INT_MAX)
: "rcx", "r11", "memory"); : "rcx", "r11", "memory");
notpossible; notpossible;
} }

View file

@ -19,6 +19,8 @@
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
// Returns granularity of memory manager. // Returns granularity of memory manager.
//
// @see sysconf(_SC_PAGE_SIZE) which is portable
getpagesize: getpagesize:
.leafprologue .leafprologue
.profilable .profilable

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/stdio/lock.internal.h" #include "libc/stdio/lock.internal.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
#include "libc/thread/thread.h"
/** /**
* Acquires reentrant lock on stdio object, blocking if needed. * Acquires reentrant lock on stdio object, blocking if needed.

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/stdio/lock.internal.h" #include "libc/stdio/lock.internal.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
#include "libc/thread/thread.h"
/** /**
* Tries to acquire reentrant stdio object lock. * Tries to acquire reentrant stdio object lock.

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/stdio/lock.internal.h" #include "libc/stdio/lock.internal.h"
#include "libc/stdio/stdio.h" #include "libc/stdio/stdio.h"
#include "libc/thread/thread.h"
/** /**
* Releases lock on stdio object. * Releases lock on stdio object.

View file

@ -4,6 +4,10 @@
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
#define PT_OWNSTACK 1
#define PT_MAINTHREAD 2
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
@ -52,9 +56,6 @@ enum PosixThreadStatus {
// - kPosixThreadZombie -> _pthread_free() will happen whenever // - kPosixThreadZombie -> _pthread_free() will happen whenever
// convenient, e.g. pthread_create() entry or atexit handler. // convenient, e.g. pthread_create() entry or atexit handler.
kPosixThreadZombie, kPosixThreadZombie,
// special main thread
kPosixThreadMain,
}; };
struct PosixThread { struct PosixThread {
@ -62,15 +63,17 @@ struct PosixThread {
void *(*start_routine)(void *); void *(*start_routine)(void *);
void *arg; // start_routine's parameter void *arg; // start_routine's parameter
void *rc; // start_routine's return value void *rc; // start_routine's return value
bool ownstack; // should we free it int flags; // see PT_* constants
int tid; // clone parent tid int tid; // clone parent tid
char *altstack; // thread sigaltstack char *altstack; // thread sigaltstack
char *tls; // bottom of tls allocation char *tls; // bottom of tls allocation
struct CosmoTib *tib; // middle of tls allocation struct CosmoTib *tib; // middle of tls allocation
jmp_buf exiter; // for pthread_exit jmp_buf exiter; // for pthread_exit
pthread_attr_t attr; pthread_attr_t attr;
struct _pthread_cleanup_buffer *cleanup;
}; };
extern struct PosixThread _pthread_main;
hidden extern pthread_spinlock_t _pthread_keys_lock; hidden extern pthread_spinlock_t _pthread_keys_lock;
hidden extern uint64_t _pthread_key_usage[(PTHREAD_KEYS_MAX + 63) / 64]; hidden extern uint64_t _pthread_key_usage[(PTHREAD_KEYS_MAX + 63) / 64];
hidden extern pthread_key_dtor _pthread_key_dtor[PTHREAD_KEYS_MAX]; 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_reschedule(struct PosixThread *) hidden;
int _pthread_setschedparam_freebsd(int, int, const struct sched_param *) hidden; int _pthread_setschedparam_freebsd(int, int, const struct sched_param *) hidden;
void _pthread_free(struct PosixThread *) hidden; void _pthread_free(struct PosixThread *) hidden;
void _pthread_cleanup(struct PosixThread *) hidden;
void _pthread_ungarbage(void) hidden; void _pthread_ungarbage(void) hidden;
void _pthread_wait(struct PosixThread *) hidden; void _pthread_wait(struct PosixThread *) hidden;
void _pthread_zombies_add(struct PosixThread *) hidden; void _pthread_zombies_add(struct PosixThread *) hidden;

View file

@ -27,6 +27,6 @@
* @return 0 on success, or error on failure * @return 0 on success, or error on failure
*/ */
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) { int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) {
*detachstate = attr->detachstate; *detachstate = attr->__detachstate;
return 0; return 0;
} }

View file

@ -26,6 +26,6 @@
*/ */
errno_t pthread_attr_getguardsize(const pthread_attr_t *attr, errno_t pthread_attr_getguardsize(const pthread_attr_t *attr,
size_t *guardsize) { size_t *guardsize) {
*guardsize = attr->guardsize; *guardsize = attr->__guardsize;
return 0; return 0;
} }

View file

@ -23,6 +23,6 @@
*/ */
int pthread_attr_getinheritsched(const pthread_attr_t *attr, int pthread_attr_getinheritsched(const pthread_attr_t *attr,
int *inheritsched) { int *inheritsched) {
*inheritsched = attr->inheritsched; *inheritsched = attr->__inheritsched;
return 0; return 0;
} }

View file

@ -23,6 +23,6 @@
*/ */
int pthread_attr_getschedparam(const pthread_attr_t *attr, int pthread_attr_getschedparam(const pthread_attr_t *attr,
struct sched_param *param) { struct sched_param *param) {
*param = (struct sched_param){attr->schedparam}; *param = (struct sched_param){attr->__schedparam};
return 0; return 0;
} }

View file

@ -22,6 +22,6 @@
* Gets thread scheduler policy attribute * Gets thread scheduler policy attribute
*/ */
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy) { int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy) {
*policy = attr->schedpolicy; *policy = attr->__schedpolicy;
return 0; return 0;
} }

View file

@ -19,6 +19,6 @@
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
int pthread_attr_getscope(const pthread_attr_t *a, int *x) { int pthread_attr_getscope(const pthread_attr_t *a, int *x) {
*x = a->scope; *x = a->__scope;
return 0; return 0;
} }

View file

@ -32,7 +32,7 @@
*/ */
errno_t pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, errno_t pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr,
size_t *stacksize) { size_t *stacksize) {
*stackaddr = attr->stackaddr; *stackaddr = attr->__stackaddr;
*stacksize = attr->stacksize; *stacksize = attr->__stacksize;
return 0; return 0;
} }

View file

@ -29,8 +29,8 @@
* @see pthread_attr_setstacksize() * @see pthread_attr_setstacksize()
*/ */
errno_t pthread_attr_getstacksize(const pthread_attr_t *a, size_t *x) { errno_t pthread_attr_getstacksize(const pthread_attr_t *a, size_t *x) {
if (a->stacksize) { if (a->__stacksize) {
*x = a->stacksize; *x = a->__stacksize;
} else { } else {
*x = GetStackSize(); *x = GetStackSize();
} }

View file

@ -26,8 +26,8 @@
*/ */
errno_t pthread_attr_init(pthread_attr_t *attr) { errno_t pthread_attr_init(pthread_attr_t *attr) {
*attr = (pthread_attr_t){ *attr = (pthread_attr_t){
.stacksize = GetStackSize(), .__stacksize = GetStackSize(),
.guardsize = PAGESIZE, .__guardsize = PAGESIZE,
}; };
return 0; return 0;
} }

View file

@ -38,7 +38,7 @@ int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) {
switch (detachstate) { switch (detachstate) {
case PTHREAD_CREATE_JOINABLE: case PTHREAD_CREATE_JOINABLE:
case PTHREAD_CREATE_DETACHED: case PTHREAD_CREATE_DETACHED:
attr->detachstate = detachstate; attr->__detachstate = detachstate;
return 0; return 0;
default: default:
return EINVAL; return EINVAL;

View file

@ -25,6 +25,6 @@
* @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) {
attr->guardsize = guardsize; attr->__guardsize = guardsize;
return 0; return 0;
} }

View file

@ -44,7 +44,7 @@ errno_t pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched) {
switch (inheritsched) { switch (inheritsched) {
case PTHREAD_INHERIT_SCHED: case PTHREAD_INHERIT_SCHED:
case PTHREAD_EXPLICIT_SCHED: case PTHREAD_EXPLICIT_SCHED:
attr->inheritsched = inheritsched; attr->__inheritsched = inheritsched;
return 0; return 0;
default: default:
assert(!"badval"); assert(!"badval");

View file

@ -43,6 +43,6 @@
int pthread_attr_setschedparam(pthread_attr_t *attr, int pthread_attr_setschedparam(pthread_attr_t *attr,
const struct sched_param *param) { const struct sched_param *param) {
if (!param) return EINVAL; if (!param) return EINVAL;
attr->schedparam = param->sched_priority; attr->__schedparam = param->sched_priority;
return 0; return 0;
} }

View file

@ -42,6 +42,6 @@
* @see sched_setscheduler() * @see sched_setscheduler()
*/ */
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) { int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) {
attr->schedpolicy = policy; attr->__schedpolicy = policy;
return 0; return 0;
} }

View file

@ -19,6 +19,6 @@
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
int pthread_attr_setscope(pthread_attr_t *a, int x) { int pthread_attr_setscope(pthread_attr_t *a, int x) {
a->scope = x; a->__scope = x;
return 0; return 0;
} }

View file

@ -65,15 +65,15 @@
errno_t pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, errno_t pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr,
size_t stacksize) { size_t stacksize) {
if (!stackaddr) { if (!stackaddr) {
attr->stackaddr = 0; attr->__stackaddr = 0;
attr->stacksize = 0; attr->__stacksize = 0;
return 0; return 0;
} }
if (stacksize < PTHREAD_STACK_MIN || if (stacksize < PTHREAD_STACK_MIN ||
(IsAsan() && !__asan_is_valid(stackaddr, stacksize))) { (IsAsan() && !__asan_is_valid(stackaddr, stacksize))) {
return EINVAL; return EINVAL;
} }
attr->stackaddr = stackaddr; attr->__stackaddr = stackaddr;
attr->stacksize = stacksize; attr->__stacksize = stacksize;
return 0; return 0;
} }

View file

@ -28,6 +28,6 @@
*/ */
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 < PTHREAD_STACK_MIN) return EINVAL; if (stacksize < PTHREAD_STACK_MIN) return EINVAL;
a->stacksize = stacksize; a->__stacksize = stacksize;
return 0; return 0;
} }

View file

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

View file

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

View file

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

View file

@ -57,11 +57,12 @@ void _pthread_wait(struct PosixThread *pt) {
} }
void _pthread_free(struct PosixThread *pt) { void _pthread_free(struct PosixThread *pt) {
if (pt->flags & PT_MAINTHREAD) return;
free(pt->tls); free(pt->tls);
if (pt->ownstack && // if ((pt->flags & PT_OWNSTACK) && //
pt->attr.stackaddr && // pt->attr.__stackaddr && //
pt->attr.stackaddr != MAP_FAILED) { pt->attr.__stackaddr != MAP_FAILED) {
if (munmap(pt->attr.stackaddr, pt->attr.stacksize)) { if (munmap(pt->attr.__stackaddr, pt->attr.__stacksize)) {
notpossible; notpossible;
} }
} }
@ -83,25 +84,19 @@ static int PosixThread(void *arg, int tid) {
notpossible; notpossible;
} }
} }
if (pt->attr.inheritsched == PTHREAD_EXPLICIT_SCHED) { if (pt->attr.__inheritsched == PTHREAD_EXPLICIT_SCHED) {
_pthread_reschedule(pt); _pthread_reschedule(pt);
} }
// set long jump handler so pthread_exit can bring control back here
if (!setjmp(pt->exiter)) { if (!setjmp(pt->exiter)) {
__get_tls()->tib_pthread = (pthread_t)pt; __get_tls()->tib_pthread = (pthread_t)pt;
pt->rc = pt->start_routine(pt->arg); pt->rc = pt->start_routine(pt->arg);
// ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup
_npassert(!pt->cleanup);
} }
if (_weaken(_pthread_key_destruct)) { // run garbage collector, call key destructors, and set change state
_weaken(_pthread_key_destruct)(0); _pthread_cleanup(pt);
} // return to clone polyfill which clears tid, wakes futex, and exits
_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);
}
return 0; return 0;
} }
@ -113,8 +108,8 @@ static int FixupCustomStackOnOpenbsd(pthread_attr_t *attr) {
size_t n; size_t n;
int e, rc; int e, rc;
uintptr_t x, y; uintptr_t x, y;
n = attr->stacksize; n = attr->__stacksize;
x = (uintptr_t)attr->stackaddr; x = (uintptr_t)attr->__stackaddr;
y = ROUNDUP(x, PAGESIZE); y = ROUNDUP(x, PAGESIZE);
n -= y - x; n -= y - x;
n = ROUNDDOWN(n, PAGESIZE); 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, if (__sys_mmap((void *)y, n, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD, MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD,
-1, 0, 0) == (void *)y) { -1, 0, 0) == (void *)y) {
attr->stackaddr = (void *)y; attr->__stackaddr = (void *)y;
attr->stacksize = n; attr->__stacksize = n;
return 0; return 0;
} else { } else {
rc = errno; rc = errno;
@ -216,7 +211,7 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
} }
// setup stack // setup stack
if (pt->attr.stackaddr) { if (pt->attr.__stackaddr) {
// caller supplied their own stack // caller supplied their own stack
// 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()) {
@ -229,37 +224,40 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// cosmo is managing the stack // cosmo is managing the stack
// 1. in mono repo optimize for tiniest stack possible // 1. in mono repo optimize for tiniest stack possible
// 2. in public world optimize to *work* regardless of memory // 2. in public world optimize to *work* regardless of memory
pt->ownstack = true; pt->flags = PT_OWNSTACK;
pt->attr.stacksize = MAX(pt->attr.stacksize, GetStackSize()); pt->attr.__stacksize = MAX(pt->attr.__stacksize, GetStackSize());
pt->attr.stacksize = _roundup2pow(pt->attr.stacksize); pt->attr.__stacksize = _roundup2pow(pt->attr.__stacksize);
pt->attr.guardsize = ROUNDUP(pt->attr.guardsize, PAGESIZE); pt->attr.__guardsize = ROUNDUP(pt->attr.__guardsize, PAGESIZE);
if (pt->attr.guardsize + PAGESIZE >= pt->attr.stacksize) { if (pt->attr.__guardsize + PAGESIZE >= pt->attr.__stacksize) {
_pthread_free(pt); _pthread_free(pt);
return EINVAL; return EINVAL;
} }
if (pt->attr.guardsize == PAGESIZE) { if (pt->attr.__guardsize == PAGESIZE) {
// user is wisely using smaller stacks with default guard size // user is wisely using smaller stacks with default guard size
pt->attr.stackaddr = mmap(0, pt->attr.stacksize, PROT_READ | PROT_WRITE, pt->attr.__stackaddr =
MAP_STACK | MAP_ANONYMOUS, -1, 0); mmap(0, pt->attr.__stacksize, PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0);
} else { } else {
// user is tuning things, performance may suffer // user is tuning things, performance may suffer
pt->attr.stackaddr = mmap(0, pt->attr.stacksize, PROT_READ | PROT_WRITE, pt->attr.__stackaddr =
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); mmap(0, pt->attr.__stacksize, PROT_READ | PROT_WRITE,
if (pt->attr.stackaddr != MAP_FAILED) { MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (pt->attr.__stackaddr != MAP_FAILED) {
if (IsOpenbsd() && if (IsOpenbsd() &&
__sys_mmap( __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, MAP_PRIVATE | MAP_FIXED | MAP_ANON_OPENBSD | MAP_STACK_OPENBSD,
-1, 0, 0) != pt->attr.stackaddr) { -1, 0, 0) != pt->attr.__stackaddr) {
notpossible; notpossible;
} }
if (pt->attr.guardsize && !IsWindows() && if (pt->attr.__guardsize && !IsWindows() &&
mprotect(pt->attr.stackaddr, pt->attr.guardsize, PROT_NONE)) { mprotect(pt->attr.__stackaddr, pt->attr.__guardsize, PROT_NONE)) {
notpossible; notpossible;
} }
} }
} }
if (pt->attr.stackaddr == MAP_FAILED) { if (pt->attr.__stackaddr == MAP_FAILED) {
rc = errno; rc = errno;
_pthread_free(pt); _pthread_free(pt);
errno = e; errno = e;
@ -269,8 +267,9 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
return EAGAIN; return EAGAIN;
} }
} }
if (IsAsan() && pt->attr.guardsize) { if (IsAsan() && pt->attr.__guardsize) {
__asan_poison(pt->attr.stackaddr, pt->attr.guardsize, kAsanStackOverflow); __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 // set initial status
switch (pt->attr.detachstate) { switch (pt->attr.__detachstate) {
case PTHREAD_CREATE_JOINABLE: case PTHREAD_CREATE_JOINABLE:
pt->status = kPosixThreadJoinable; pt->status = kPosixThreadJoinable;
break; break;
@ -294,8 +293,8 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
} }
// launch PosixThread(pt) in new thread // launch PosixThread(pt) in new thread
if (clone(PosixThread, pt->attr.stackaddr, if (clone(PosixThread, pt->attr.__stackaddr,
pt->attr.stacksize - (IsOpenbsd() ? 16 : 0), pt->attr.__stacksize - (IsOpenbsd() ? 16 : 0),
CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_SETTID |
CLONE_CHILD_CLEARTID, CLONE_CHILD_CLEARTID,

View file

@ -16,19 +16,45 @@
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/dce.h"
#include "libc/limits.h"
#include "libc/mem/gc.h" #include "libc/mem/gc.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
#include "third_party/nsync/futex.internal.h"
STATIC_YOINK("_pthread_main");
/** /**
* Terminates current POSIX thread. * Terminates current POSIX thread.
* *
* If this function is called from the main thread, or a thread created * For example, a thread could terminate early as follows:
* 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 * pthread_exit((void *)123);
* only be reported to the parent process on Linux, FreeBSD and Windows *
* 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() * @param rc is reported later to pthread_join()
* @threadsafe * @threadsafe
@ -36,11 +62,32 @@
*/ */
wontreturn void pthread_exit(void *rc) { wontreturn void pthread_exit(void *rc) {
struct PosixThread *pt; struct PosixThread *pt;
pt = (struct PosixThread *)pthread_self(); struct _pthread_cleanup_buffer *cb;
if (pt->status != kPosixThreadMain) { pt = (struct PosixThread *)__get_tls()->tib_pthread;
pt->rc = rc; pt->rc = rc;
_gclongjmp(pt->exiter, 1); // 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 { } 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);
} }
} }

View file

@ -16,12 +16,51 @@
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/intrin/atomic.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.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) { int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) {
struct PosixThread *pt = (struct PosixThread *)thread; struct PosixThread *pt = (struct PosixThread *)thread;
memcpy(attr, &pt->attr, sizeof(pt->attr)); 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; return 0;
} }

View file

@ -25,7 +25,7 @@
int pthread_getschedparam(pthread_t thread, int *policy, int pthread_getschedparam(pthread_t thread, int *policy,
struct sched_param *param) { struct sched_param *param) {
struct PosixThread *pt = (struct PosixThread *)thread; struct PosixThread *pt = (struct PosixThread *)thread;
*policy = pt->attr.schedpolicy; *policy = pt->attr.__schedpolicy;
*param = (struct sched_param){pt->attr.schedparam}; *param = (struct sched_param){pt->attr.__schedparam};
return 0; return 0;
} }

View file

@ -24,14 +24,16 @@
/** /**
* Waits for thread to terminate. * Waits for thread to terminate.
* *
* @param value_ptr if non-null will receive pthread_exit() argument
* @return 0 on success, or errno with error * @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 * @returnserrno
* @threadsafe * @threadsafe
*/ */
int pthread_join(pthread_t thread, void **value_ptr) { int pthread_join(pthread_t thread, void **value_ptr) {
struct PosixThread *pt; struct PosixThread *pt;
if (thread == pthread_self()) { if (thread == __get_tls()->tib_pthread) {
return EDEADLK; return EDEADLK;
} }
if (!(pt = (struct PosixThread *)thread) || // if (!(pt = (struct PosixThread *)thread) || //

View file

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

View file

@ -25,13 +25,14 @@
int _pthread_reschedule(struct PosixThread *pt) { int _pthread_reschedule(struct PosixThread *pt) {
int rc, e = errno; 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()) { if (IsNetbsd()) {
rc = sys_sched_setparam_netbsd(0, pt->tid, pt->attr.schedpolicy, &param); rc = sys_sched_setparam_netbsd(0, pt->tid, policy, &param);
} else if (IsLinux()) { } else if (IsLinux()) {
rc = sys_sched_setscheduler(pt->tid, pt->attr.schedpolicy, &param); rc = sys_sched_setscheduler(pt->tid, policy, &param);
} else if (IsFreebsd()) { } else if (IsFreebsd()) {
rc = _pthread_setschedparam_freebsd(pt->tid, pt->attr.schedpolicy, &param); rc = _pthread_setschedparam_freebsd(pt->tid, policy, &param);
} else { } else {
rc = enosys(); rc = enosys();
} }

View file

@ -16,21 +16,14 @@
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/calls/calls.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
STATIC_YOINK("_pthread_main");
/** /**
* Returns current POSIX thread. * Returns current POSIX thread.
*/ */
pthread_t pthread_self(void) { pthread_t pthread_self(void) {
return __get_tls()->tib_pthread; 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;
}

View file

@ -45,7 +45,7 @@ int pthread_setschedparam(pthread_t thread, int policy,
const struct sched_param *param) { const struct sched_param *param) {
struct PosixThread *pt = (struct PosixThread *)thread; struct PosixThread *pt = (struct PosixThread *)thread;
if (!param) return EINVAL; if (!param) return EINVAL;
pt->attr.schedpolicy = policy; pt->attr.__schedpolicy = policy;
pt->attr.schedparam = param->sched_priority; pt->attr.__schedparam = param->sched_priority;
return _pthread_reschedule(pt); return _pthread_reschedule(pt);
} }

View file

@ -16,17 +16,17 @@
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/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/nexgen32e/gc.internal.h" #include "libc/nexgen32e/gc.internal.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
void _pthread_ungarbage(void) { void _pthread_ungarbage(void) {
int i;
struct Garbages *g; struct Garbages *g;
if ((g = __get_tls()->tib_garbages)) { if ((g = __get_tls()->tib_garbages)) {
// _pthread_exit() uses _gclongjmp() so if this assertion fails, for (i = g->i; i--;) {
// then the likely cause is the thread used gc() with longjmp(). ((void (*)(intptr_t))g->p[i].fn)(g->p[i].arg);
assert(!g->i); }
free(g->p); free(g->p);
free(g); free(g);
} }

View file

@ -77,16 +77,23 @@ typedef struct pthread_barrier_s {
} pthread_barrier_t; } pthread_barrier_t;
typedef struct pthread_attr_s { typedef struct pthread_attr_s {
char detachstate; char __detachstate;
char inheritsched; char __inheritsched;
int schedparam; int __schedparam;
int schedpolicy; int __schedpolicy;
int scope; int __scope;
unsigned guardsize; unsigned __guardsize;
unsigned stacksize; unsigned __stacksize;
char *stackaddr; char *__stackaddr;
} pthread_attr_t; } 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 *), int pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *),
void *); void *);
@ -168,6 +175,18 @@ int pthread_barrier_wait(pthread_barrier_t *);
int pthread_barrier_destroy(pthread_barrier_t *); int pthread_barrier_destroy(pthread_barrier_t *);
int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *, int pthread_barrier_init(pthread_barrier_t *, const pthread_barrierattr_t *,
unsigned); 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_init(pSpin, multiprocess) ((pSpin)->_lock = 0, 0)
#define pthread_spin_destroy(pSpin) ((pSpin)->_lock = -1, 0) #define pthread_spin_destroy(pSpin) ((pSpin)->_lock = -1, 0)

View file

@ -1,6 +1,5 @@
#ifndef COSMOPOLITAN_LIBC_THREAD_TLS_H_ #ifndef COSMOPOLITAN_LIBC_THREAD_TLS_H_
#define COSMOPOLITAN_LIBC_THREAD_TLS_H_ #define COSMOPOLITAN_LIBC_THREAD_TLS_H_
#include "libc/thread/thread.h"
#define TLS_ALIGNMENT 64 #define TLS_ALIGNMENT 64
@ -33,7 +32,7 @@ struct CosmoTib {
void *tib_reserved5; void *tib_reserved5;
void *tib_reserved6; void *tib_reserved6;
void *tib_reserved7; void *tib_reserved7;
void *tib_keys[PTHREAD_KEYS_MAX]; void *tib_keys[128];
}; };
extern int __threaded; extern int __threaded;

View file

@ -16,71 +16,10 @@
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/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/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.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" #include "libc/thread/wait0.internal.h"
#include "third_party/nsync/futex.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);
}
/** /**
* Blocks until memory location becomes zero. * 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 * 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 * 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 * 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 futexes on Linux, FreeBSD, OpenBSD, and Windows. On other
* uses polling with exponential backoff. * platforms this uses polling with exponential backoff.
*/ */
void _wait0(const atomic_int *ctid) { void _wait0(const atomic_int *ctid) {
int x; int x;
struct timespec ts = {0, 1}; while ((x = atomic_load_explicit(ctid, memory_order_relaxed))) {
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { nsync_futex_wait_((int *)ctid, x, !IsWindows(), 0);
if (IsLinux() || IsOpenbsd() || IsWindows()) {
_wait0_futex(ctid, x);
} else {
_wait0_poll(&ts);
}
}
if (IsOpenbsd()) {
sched_yield(); // TODO(jart): whhhy?
} }
} }

View file

@ -1714,7 +1714,7 @@ OnError:
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
ShowCrashReports(); // ShowCrashReports();
// we don't have proper futexes on these platforms // we don't have proper futexes on these platforms
// so choose a smaller number of workers // so choose a smaller number of workers

View file

@ -58,6 +58,7 @@
#include "libc/testlib/subprocess.h" #include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h" #include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h" #include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
#include "libc/time/time.h" #include "libc/time/time.h"
#include "libc/x/x.h" #include "libc/x/x.h"

View file

@ -16,13 +16,14 @@
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/atomic.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/struct/sched_param.h" #include "libc/calls/struct/sched_param.h"
#include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigaction.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/mem/gc.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/nexgen32e/nexgen32e.h" #include "libc/nexgen32e/nexgen32e.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
@ -48,7 +49,6 @@ void SetUp(void) {
void TriggerSignal(void) { void TriggerSignal(void) {
sched_yield(); sched_yield();
/* kprintf("raising at %p\n", __builtin_frame_address(0)); */
raise(SIGUSR1); raise(SIGUSR1);
sched_yield(); sched_yield();
} }
@ -183,19 +183,90 @@ TEST(pthread_detach, testCustomStack_withReallySmallSize) {
free(stk); free(stk);
} }
TEST(pthread_exit, mainThreadWorks) { void *JoinMainWorker(void *arg) {
// _Exit1() can't set process exit code on XNU/NetBSD/OpenBSD. void *rc;
if (IsLinux() || IsFreebsd() || IsWindows()) { pthread_t main_thread = (pthread_t)arg;
SPAWN(fork); _gc(malloc(32));
pthread_exit((void *)2); _gc(malloc(32));
EXITS(2); ASSERT_EQ(0, pthread_join(main_thread, &rc));
} else { ASSERT_EQ(123, (intptr_t)rc);
SPAWN(fork); return 0;
pthread_exit((void *)0);
EXITS(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) { static void CreateJoin(void) {
pthread_t id; pthread_t id;
ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0)); ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0));