Make detached threads work better

This change adds a double linked list of threads, so that pthread_exit()
will know when it should call exit() from an orphaned child. This change
also improves ftrace and strace logging.
This commit is contained in:
Justine Tunney 2022-11-09 03:58:57 -08:00
parent b74d8c1acd
commit cee6871710
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
37 changed files with 638 additions and 314 deletions

View file

@ -953,7 +953,7 @@ static void *__asan_morgue_add(void *p) {
p, memory_order_acq_rel); p, memory_order_acq_rel);
} }
static void __asan_morgue_flush(void) { __attribute__((__destructor__)) static void __asan_morgue_flush(void) {
unsigned i; unsigned i;
for (i = 0; i < ARRAYLEN(__asan_morgue.p); ++i) { for (i = 0; i < ARRAYLEN(__asan_morgue.p); ++i) {
if (atomic_load_explicit(__asan_morgue.p + i, memory_order_acquire)) { if (atomic_load_explicit(__asan_morgue.p + i, memory_order_acquire)) {
@ -1461,8 +1461,8 @@ static textstartup void __asan_shadow_existing_mappings(void) {
__asan_poison((void *)GetStackAddr(), PAGESIZE, kAsanStackOverflow); __asan_poison((void *)GetStackAddr(), PAGESIZE, kAsanStackOverflow);
} }
textstartup void __asan_init(int argc, char **argv, char **envp, __attribute__((__constructor__)) void __asan_init(int argc, char **argv,
intptr_t *auxv) { char **envp, intptr_t *auxv) {
static bool once; static bool once;
if (!_cmpxchg(&once, false, true)) return; if (!_cmpxchg(&once, false, true)) return;
if (IsWindows() && NtGetVersion() < kNtVersionWindows10) { if (IsWindows() && NtGetVersion() < kNtVersionWindows10) {
@ -1497,13 +1497,3 @@ textstartup void __asan_init(int argc, char **argv, char **envp,
STRACE("/_/ \\_\\____/_/ \\_\\_| \\_|"); STRACE("/_/ \\_\\____/_/ \\_\\_| \\_|");
STRACE("cosmopolitan memory safety module initialized"); STRACE("cosmopolitan memory safety module initialized");
} }
static textstartup void __asan_ctor(void) {
if (_weaken(__cxa_atexit)) {
_weaken(__cxa_atexit)(__asan_morgue_flush, NULL, NULL);
}
}
const void *const g_asan_ctor[] initarray = {
__asan_ctor,
};

View file

@ -18,8 +18,6 @@
*/ */
#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/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"
@ -47,7 +45,6 @@ __msabi extern typeof(ExitThread) *const __imp_ExitThread;
privileged wontreturn void _Exit1(int rc) { privileged wontreturn void _Exit1(int rc) {
char cf; char cf;
int ax, dx, di, si; int ax, dx, di, si;
STRACE("_Exit1(%d)", rc);
if (!IsWindows() && !IsMetal()) { if (!IsWindows() && !IsMetal()) {
// exit() on Linux // exit() on Linux
// thr_exit() on FreeBSD // thr_exit() on FreeBSD

View file

@ -18,6 +18,7 @@
*/ */
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/calls/state.internal.h" #include "libc/calls/state.internal.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/extend.internal.h" #include "libc/intrin/extend.internal.h"
#include "libc/intrin/pushpop.h" #include "libc/intrin/pushpop.h"
#include "libc/intrin/weaken.h" #include "libc/intrin/weaken.h"
@ -38,7 +39,7 @@ static textwindows dontinline void SetupWinStd(struct Fds *fds, int i, int x) {
if (!h || h == -1) return; if (!h || h == -1) return;
fds->p[i].kind = pushpop(kFdFile); fds->p[i].kind = pushpop(kFdFile);
fds->p[i].handle = h; fds->p[i].handle = h;
fds->f = i + 1; atomic_store_explicit(&fds->f, i + 1, memory_order_relaxed);
} }
textstartup void InitializeFileDescriptors(void) { textstartup void InitializeFileDescriptors(void) {
@ -49,7 +50,7 @@ textstartup void InitializeFileDescriptors(void) {
fds = VEIL("r", &g_fds); fds = VEIL("r", &g_fds);
fds->p = fds->e = (void *)kMemtrackFdsStart; fds->p = fds->e = (void *)kMemtrackFdsStart;
fds->n = 4; fds->n = 4;
fds->f = 3; atomic_store_explicit(&fds->f, 3, memory_order_relaxed);
fds->e = _extend(fds->p, fds->n * sizeof(*fds->p), fds->e, MAP_PRIVATE, fds->e = _extend(fds->p, fds->n * sizeof(*fds->p), fds->e, MAP_PRIVATE,
kMemtrackFdsStart + kMemtrackFdsSize); kMemtrackFdsStart + kMemtrackFdsSize);
if (IsMetal()) { if (IsMetal()) {

View file

@ -16,16 +16,26 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/intrin/atomic.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"
/** /**
* Gets value of TLS slot for current thread. * Gets value of TLS slot for current thread.
*
* If `k` wasn't created by pthread_key_create() then the behavior is
* undefined. If `k` was unregistered earlier by pthread_key_delete()
* then the behavior is undefined.
*/ */
void *pthread_getspecific(pthread_key_t key) { void *pthread_getspecific(pthread_key_t k) {
if (0 <= key && key < PTHREAD_KEYS_MAX) { // "The effect of calling pthread_getspecific() or
return __get_tls()->tib_keys[key]; // pthread_setspecific() with a key value not obtained from
} else { // pthread_key_create() or after key has been deleted with
return 0; // pthread_key_delete() is undefined."
} // ──Quoth POSIX.1-2017
_unassert(0 <= k && k < PTHREAD_KEYS_MAX);
_unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
return __get_tls()->tib_keys[k];
} }

View file

@ -18,18 +18,24 @@
*/ */
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
/** /**
* Allocates TLS slot. * Allocates TLS slot.
* *
* If `dtor` is non-null, then it'll be called upon thread exit when the * This function creates a thread-local storage registration, that will
* key's value is nonzero. The key's value is set to zero before it gets * apply to all threads. The new identifier is written to `key`, and it
* called. The ordering for multiple destructor calls is unspecified. * can be passed to the pthread_setspecific() and pthread_getspecific()
* functions to set and get its associated value. Each thread will have
* its key value initialized to zero upon creation. It is also possible
* to use pthread_key_delete() to unregister a key.
* *
* The result should be passed to pthread_key_delete() later. * If `dtor` is non-null, then it'll be called upon pthread_exit() when
* the key's value is nonzero. The key's value is set to zero before it
* is called. The ordering of multiple destructor calls is unspecified.
* The same key can be destroyed `PTHREAD_DESTRUCTOR_ITERATIONS` times,
* in cases where it gets set again by a destructor.
* *
* @param key is set to the allocated key on success * @param key is set to the allocated key on success
* @param dtor specifies an optional destructor callback * @param dtor specifies an optional destructor callback
@ -42,9 +48,9 @@ int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) {
if (!dtor) dtor = (pthread_key_dtor)-1; if (!dtor) dtor = (pthread_key_dtor)-1;
for (i = 0; i < PTHREAD_KEYS_MAX; ++i) { for (i = 0; i < PTHREAD_KEYS_MAX; ++i) {
if (!(expect = atomic_load_explicit(_pthread_key_dtor + i, if (!(expect = atomic_load_explicit(_pthread_key_dtor + i,
memory_order_relaxed)) && memory_order_acquire)) &&
atomic_compare_exchange_strong_explicit(_pthread_key_dtor + i, &expect, atomic_compare_exchange_strong_explicit(_pthread_key_dtor + i, &expect,
dtor, memory_order_relaxed, dtor, memory_order_release,
memory_order_relaxed)) { memory_order_relaxed)) {
*key = i; *key = i;
return 0; return 0;
@ -52,7 +58,3 @@ int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) {
} }
return EAGAIN; return EAGAIN;
} }
__attribute__((__constructor__)) static textstartup void _pthread_key_init() {
atexit(_pthread_key_destruct);
}

View file

@ -16,6 +16,7 @@
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/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
@ -24,16 +25,18 @@
/** /**
* Deletes TLS slot. * Deletes TLS slot.
* *
* This function should only be called if all threads have finished
* using the key registration. If a key is used after being deleted
* then the behavior is undefined. If `k` was not registered by the
* pthread_key_create() function then the behavior is undefined.
*
* @param key was created by pthread_key_create() * @param key was created by pthread_key_create()
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @raise EINVAL if `key` isn't valid
*/ */
int pthread_key_delete(pthread_key_t key) { int pthread_key_delete(pthread_key_t k) {
uint64_t mask; uint64_t mask;
if (key < PTHREAD_KEYS_MAX) { _unassert(0 <= k && k < PTHREAD_KEYS_MAX);
atomic_store_explicit(_pthread_key_dtor + key, 0, memory_order_relaxed); _unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
return 0; atomic_store_explicit(_pthread_key_dtor + k, 0, memory_order_release);
} else { return 0;
return EINVAL;
}
} }

View file

@ -16,18 +16,27 @@
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/errno.h" #include "libc/assert.h"
#include "libc/intrin/atomic.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"
/** /**
* Sets value of TLS slot for current thread. * Sets value of TLS slot for current thread.
*
* If `k` wasn't created by pthread_key_create() then the behavior is
* undefined. If `k` was unregistered earlier by pthread_key_delete()
* then the behavior is undefined.
*/ */
int pthread_setspecific(pthread_key_t key, const void *val) { int pthread_setspecific(pthread_key_t k, const void *val) {
if (0 <= key && key < PTHREAD_KEYS_MAX) { // "The effect of calling pthread_getspecific() or
__get_tls()->tib_keys[key] = val; // pthread_setspecific() with a key value not obtained from
return 0; // pthread_key_create() or after key has been deleted with
} else { // pthread_key_delete() is undefined."
return EINVAL; // ──Quoth POSIX.1-2017
} _unassert(0 <= k && k < PTHREAD_KEYS_MAX);
_unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
__get_tls()->tib_keys[k] = val;
return 0;
} }

View file

@ -16,13 +16,19 @@
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/thread/thread.h" #include "libc/thread/thread.h"
#ifdef pthread_spin_destroy
#undef pthread_spin_destroy
#endif
/** /**
* Destroys spin lock. * Destroys spin lock.
* *
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
*/ */
errno_t(pthread_spin_destroy)(pthread_spinlock_t *spin) { errno_t pthread_spin_destroy(pthread_spinlock_t *spin) {
return pthread_spin_destroy(spin); atomic_store_explicit(&spin->_lock, -1, memory_order_relaxed);
return 0;
} }

View file

@ -16,8 +16,13 @@
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/thread/thread.h" #include "libc/thread/thread.h"
#ifdef pthread_spin_init
#undef pthread_spin_init
#endif
/** /**
* Initializes spin lock. * Initializes spin lock.
* *
@ -27,6 +32,7 @@
* @see pthread_spin_destroy * @see pthread_spin_destroy
* @see pthread_spin_lock * @see pthread_spin_lock
*/ */
errno_t(pthread_spin_init)(pthread_spinlock_t *spin, int pshared) { errno_t pthread_spin_init(pthread_spinlock_t *spin, int pshared) {
return pthread_spin_init(spin, pshared); atomic_store_explicit(&spin->_lock, 0, memory_order_relaxed);
return 0;
} }

View file

@ -16,20 +16,16 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/intrin/atomic.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#ifdef pthread_spin_lock
#undef pthread_spin_lock
#endif
/** /**
* Acquires spin lock. * Acquires spin lock, e.g.
*
* If the lock is already held, this function will wait for it to become
* available. No genuine error conditions are currently defined. This is
* similar to pthread_mutex_lock() except spin locks are much simpler so
* this API is able to offer a performance advantage in situations where
* scalable contention handling isn't necessary. Spinlocks are also very
* small especially in MODE=tiny where a lock needs 16 bytes of code and
* unlocking needs just 5 bytes. The lock object also only takes 1 byte.
*
* The posixly correct way to use this API is as follows:
* *
* pthread_spinlock_t lock; * pthread_spinlock_t lock;
* pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE); * pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
@ -38,18 +34,20 @@
* pthread_spin_unlock(&lock); * pthread_spin_unlock(&lock);
* pthread_spin_destroy(&lock); * pthread_spin_destroy(&lock);
* *
* Cosmopolitan permits succinct notation for spin locks: * This function has undefined behavior when `spin` wasn't intialized,
* * was destroyed, or if the lock's already held by the calling thread.
* pthread_spinlock_t lock = {0};
* pthread_spin_lock(&lock);
* // do work...
* pthread_spin_unlock(&lock);
* *
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @see pthread_spin_trylock * @see pthread_spin_trylock
* @see pthread_spin_unlock * @see pthread_spin_unlock
* @see pthread_spin_init * @see pthread_spin_init
*/ */
errno_t(pthread_spin_lock)(pthread_spinlock_t *spin) { errno_t pthread_spin_lock(pthread_spinlock_t *spin) {
return pthread_spin_lock(spin); int x;
for (;;) {
x = atomic_exchange_explicit(&spin->_lock, 1, memory_order_acquire);
if (!x) break;
_unassert(x == 1);
}
return 0;
} }

View file

@ -16,17 +16,28 @@
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/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#ifdef pthread_spin_trylock
#undef pthread_spin_trylock
#endif
/** /**
* Acquires spin lock if available. * Acquires spin lock if available.
* *
* Unlike pthread_spin_lock() this function won't block, and instead * This function has undefined behavior when `spin` wasn't intialized,
* returns an error immediately if the spinlock couldn't be acquired * was destroyed, or if the lock's already held by the calling thread.
* *
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @raise EBUSY if lock is already held * @raise EBUSY if lock is already held
*/ */
errno_t(pthread_spin_trylock)(pthread_spinlock_t *spin) { errno_t pthread_spin_trylock(pthread_spinlock_t *spin) {
return pthread_spin_trylock(spin); int x;
x = atomic_exchange_explicit(&spin->_lock, 1, memory_order_acquire);
if (!x) return 0;
_unassert(x == 1);
return EBUSY;
} }

View file

@ -16,14 +16,23 @@
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/thread/thread.h" #include "libc/thread/thread.h"
#ifdef pthread_spin_unlock
#undef pthread_spin_unlock
#endif
/** /**
* Releases spin lock. * Releases spin lock.
* *
* Calling this function when the lock isn't held by the calling thread
* has undefined behavior.
*
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @see pthread_spin_lock * @see pthread_spin_lock
*/ */
errno_t(pthread_spin_unlock)(pthread_spinlock_t *spin) { errno_t pthread_spin_unlock(pthread_spinlock_t *spin) {
return pthread_spin_unlock(spin); atomic_store_explicit(&spin->_lock, 0, memory_order_release);
return 0;
} }

View file

@ -16,6 +16,7 @@
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/blockcancel.internal.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/struct/rusage.internal.h" #include "libc/calls/struct/rusage.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
@ -62,10 +63,12 @@ static int PrintBacktraceUsingAddr2line(int fd, const struct StackFrame *bp) {
} }
if (!PLEDGED(STDIO) || !PLEDGED(EXEC) || !PLEDGED(EXEC)) { if (!PLEDGED(STDIO) || !PLEDGED(EXEC) || !PLEDGED(EXEC)) {
ShowHint("won't print addr2line backtrace because pledge");
return -1; return -1;
} }
if (!(debugbin = FindDebugBinary())) { if (!(debugbin = FindDebugBinary())) {
ShowHint("won't print addr2line backtrace because no debug binary");
return -1; return -1;
} }
@ -171,11 +174,14 @@ static int PrintBacktrace(int fd, const struct StackFrame *bp) {
return 0; return 0;
} }
} }
#else
ShowHint("won't print addr2line backtrace because no dwarf");
#endif #endif
return PrintBacktraceUsingSymbols(fd, bp, GetSymbolTable()); return PrintBacktraceUsingSymbols(fd, bp, GetSymbolTable());
} }
void ShowBacktrace(int fd, const struct StackFrame *bp) { void ShowBacktrace(int fd, const struct StackFrame *bp) {
BLOCK_CANCELLATIONS;
#ifdef __FNO_OMIT_FRAME_POINTER__ #ifdef __FNO_OMIT_FRAME_POINTER__
/* asan runtime depends on this function */ /* asan runtime depends on this function */
ftrace_enabled(-1); ftrace_enabled(-1);
@ -189,4 +195,5 @@ void ShowBacktrace(int fd, const struct StackFrame *bp) {
"\t-D__FNO_OMIT_FRAME_POINTER__\n" "\t-D__FNO_OMIT_FRAME_POINTER__\n"
"\t-fno-omit-frame-pointer\n"); "\t-fno-omit-frame-pointer\n");
#endif #endif
ALLOW_CANCELLATIONS;
} }

View file

@ -108,7 +108,7 @@ WinThreadEntry(int rdi, // rcx
*wt->ztid = 0; *wt->ztid = 0;
__imp_WakeByAddressAll(wt->ztid); __imp_WakeByAddressAll(wt->ztid);
// since we didn't indirect this function through NT2SYSV() it's not // since we didn't indirect this function through NT2SYSV() it's not
// safe to simply return, and as such, we just call ExitThread(). // safe to simply return, and as such, we need ExitThread().
__imp_ExitThread(rc); __imp_ExitThread(rc);
notpossible; notpossible;
} }

View file

@ -28,15 +28,19 @@
#include "libc/runtime/internal.h" #include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
#define _TLSZ ((intptr_t)_tls_size) #define _TLSZ ((intptr_t)_tls_size)
#define _TLDZ ((intptr_t)_tdata_size) #define _TLDZ ((intptr_t)_tdata_size)
#define _TIBZ sizeof(struct CosmoTib) #define _TIBZ sizeof(struct CosmoTib)
struct PosixThread _pthread_main;
extern unsigned char __tls_mov_nt_rax[]; extern unsigned char __tls_mov_nt_rax[];
extern unsigned char __tls_add_nt_rax[]; extern unsigned char __tls_add_nt_rax[];
nsync_dll_list_ _pthread_list;
pthread_spinlock_t _pthread_lock;
static struct PosixThread _pthread_main;
_Alignas(TLS_ALIGNMENT) static char __static_tls[5008]; _Alignas(TLS_ALIGNMENT) static char __static_tls[5008];
/** /**
@ -79,6 +83,7 @@ void __enable_tls(void) {
size_t siz; size_t siz;
struct CosmoTib *tib; struct CosmoTib *tib;
char *mem, *tls; char *mem, *tls;
siz = ROUNDUP(_TLSZ + _TIBZ, _Alignof(__static_tls)); siz = ROUNDUP(_TLSZ + _TIBZ, _Alignof(__static_tls));
if (siz <= sizeof(__static_tls)) { if (siz <= sizeof(__static_tls)) {
// if tls requirement is small then use the static tls block // if tls requirement is small then use the static tls block
@ -95,12 +100,15 @@ void __enable_tls(void) {
mem = _weaken(_mapanon)(siz); mem = _weaken(_mapanon)(siz);
_npassert(mem); _npassert(mem);
} }
if (IsAsan()) { if (IsAsan()) {
// poison the space between .tdata and .tbss // poison the space between .tdata and .tbss
__asan_poison(mem + (intptr_t)_tdata_size, __asan_poison(mem + (intptr_t)_tdata_size,
(intptr_t)_tbss_offset - (intptr_t)_tdata_size, (intptr_t)_tbss_offset - (intptr_t)_tdata_size,
kAsanProtected); kAsanProtected);
} }
// initialize main thread tls memory
tib = (struct CosmoTib *)(mem + siz - _TIBZ); tib = (struct CosmoTib *)(mem + siz - _TIBZ);
tls = mem + siz - _TIBZ - _TLSZ; tls = mem + siz - _TIBZ - _TLSZ;
tib->tib_self = tib; tib->tib_self = tib;
@ -117,9 +125,16 @@ void __enable_tls(void) {
tid = sys_gettid(); tid = sys_gettid();
} }
atomic_store_explicit(&tib->tib_tid, tid, memory_order_relaxed); atomic_store_explicit(&tib->tib_tid, tid, memory_order_relaxed);
_pthread_main.ptid = tid;
// initialize posix threads
_pthread_main.tib = tib; _pthread_main.tib = tib;
_pthread_main.flags = PT_MAINTHREAD; _pthread_main.flags = PT_STATIC;
_pthread_main.list.prev = _pthread_main.list.next = //
_pthread_list = VEIL("r", &_pthread_main.list);
_pthread_main.list.container = &_pthread_main;
atomic_store_explicit(&_pthread_main.ptid, tid, memory_order_relaxed);
// copy in initialized data section
__repmovsb(tls, _tdata_start, _TLDZ); __repmovsb(tls, _tdata_start, _TLDZ);
// ask the operating system to change the x86 segment register // ask the operating system to change the x86 segment register

View file

@ -24,6 +24,7 @@
#include "libc/calls/wincrash.internal.h" #include "libc/calls/wincrash.internal.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/fmt/itoa.h" #include "libc/fmt/itoa.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/directmap.internal.h" #include "libc/intrin/directmap.internal.h"
#include "libc/intrin/kprintf.h" #include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.internal.h" #include "libc/intrin/strace.internal.h"
@ -237,7 +238,7 @@ textwindows void WinMainForked(void) {
for (i = 0; i < fds->n; ++i) { for (i = 0; i < fds->n; ++i) {
if (fds->p[i].kind == kFdProcess) { if (fds->p[i].kind == kFdProcess) {
fds->p[i].kind = 0; fds->p[i].kind = 0;
fds->f = MIN(i, fds->f); atomic_store_explicit(&fds->f, MIN(i, fds->f), memory_order_relaxed);
} }
} }

View file

@ -218,6 +218,8 @@ void testlib_runtestcases(testfn_t *start, testfn_t *end, testfn_t warmup) {
} }
if (_weaken(SetUpOnce)) _weaken(SetUpOnce)(); if (_weaken(SetUpOnce)) _weaken(SetUpOnce)();
for (x = 0, fn = start; fn != end; ++fn) { for (x = 0, fn = start; fn != end; ++fn) {
STRACE("");
STRACE("# setting up %t", fn);
if (_weaken(testlib_enable_tmp_setup_teardown)) SetupTmpDir(); if (_weaken(testlib_enable_tmp_setup_teardown)) SetupTmpDir();
if (_weaken(SetUp)) _weaken(SetUp)(); if (_weaken(SetUp)) _weaken(SetUp)();
errno = 0; errno = 0;
@ -229,6 +231,7 @@ void testlib_runtestcases(testfn_t *start, testfn_t *end, testfn_t warmup) {
STRACE("# running test %t", fn); STRACE("# running test %t", fn);
(*fn)(); (*fn)();
STRACE(""); STRACE("");
STRACE("# tearing down %t", fn);
if (!IsWindows()) sys_getpid(); if (!IsWindows()) sys_getpid();
if (_weaken(TearDown)) _weaken(TearDown)(); if (_weaken(TearDown)) _weaken(TearDown)();
if (_weaken(testlib_enable_tmp_setup_teardown)) TearDownTmpDir(); if (_weaken(testlib_enable_tmp_setup_teardown)) TearDownTmpDir();

View file

@ -5,14 +5,16 @@
#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"
#include "third_party/nsync/dll.h"
#define PT_OWNSTACK 1 #define PT_OWNSTACK 1
#define PT_MAINTHREAD 2 #define PT_STATIC 2
#define PT_ASYNC 4 #define PT_ASYNC 4
#define PT_NOCANCEL 8 #define PT_NOCANCEL 8
#define PT_MASKED 16 #define PT_MASKED 16
#define PT_INCANCEL 32 #define PT_INCANCEL 32
#define PT_OPENBSD_KLUDGE 64 #define PT_OPENBSD_KLUDGE 64
#define PT_EXITING 128
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
@ -66,14 +68,15 @@ struct PosixThread {
int flags; // 0x00: see PT_* constants int flags; // 0x00: see PT_* constants
_Atomic(int) cancelled; // 0x04: thread has bad beliefs _Atomic(int) cancelled; // 0x04: thread has bad beliefs
_Atomic(enum PosixThreadStatus) status; _Atomic(enum PosixThreadStatus) status;
_Atomic(int) ptid; // transitions 0 → tid _Atomic(int) ptid; // transitions 0 → tid
void *(*start)(void *); // creation callback void *(*start)(void *); // creation callback
void *arg; // start's parameter void *arg; // start's parameter
void *rc; // start's return value void *rc; // start's return value
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 nsync_dll_element_ list; // list of threads
jmp_buf exiter; // for pthread_exit
pthread_attr_t attr; pthread_attr_t attr;
sigset_t sigmask; sigset_t sigmask;
struct _pthread_cleanup_buffer *cleanup; struct _pthread_cleanup_buffer *cleanup;
@ -81,24 +84,19 @@ struct PosixThread {
typedef void (*atfork_f)(void); typedef void (*atfork_f)(void);
extern struct PosixThread _pthread_main; extern nsync_dll_list_ _pthread_list;
extern pthread_spinlock_t _pthread_lock;
extern _Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX] _Hide; extern _Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX] _Hide;
int _pthread_atfork(atfork_f, atfork_f, atfork_f) _Hide; int _pthread_atfork(atfork_f, atfork_f, atfork_f) _Hide;
int _pthread_reschedule(struct PosixThread *) _Hide; int _pthread_reschedule(struct PosixThread *) _Hide;
int _pthread_setschedparam_freebsd(int, int, const struct sched_param *) _Hide; int _pthread_setschedparam_freebsd(int, int, const struct sched_param *) _Hide;
int _pthread_wait(struct PosixThread *) _Hide; void _pthread_zombify(struct PosixThread *) _Hide;
void _pthread_free(struct PosixThread *) _Hide; void _pthread_free(struct PosixThread *) _Hide;
void _pthread_cleanup(struct PosixThread *) _Hide;
void _pthread_ungarbage(void) _Hide;
void _pthread_zombies_add(struct PosixThread *) _Hide;
void _pthread_zombies_purge(void) _Hide;
void _pthread_zombies_decimate(void) _Hide;
void _pthread_zombies_harvest(void) _Hide;
void _pthread_key_destruct(void) _Hide;
void _pthread_onfork_prepare(void) _Hide; void _pthread_onfork_prepare(void) _Hide;
void _pthread_onfork_parent(void) _Hide; void _pthread_onfork_parent(void) _Hide;
void _pthread_onfork_child(void) _Hide; void _pthread_onfork_child(void) _Hide;
void _pthread_ungarbage(void) _Hide;
int _pthread_cancel_sys(void) _Hide; int _pthread_cancel_sys(void) _Hide;
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_

View file

@ -20,6 +20,7 @@
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/intrin/kmalloc.h" #include "libc/intrin/kmalloc.h"
#include "libc/mem/mem.h"
#include "libc/runtime/memtrack.internal.h" #include "libc/runtime/memtrack.internal.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
@ -34,6 +35,14 @@ static struct AtForks {
} * list; } * list;
} _atforks; } _atforks;
static void _pthread_purge(void) {
nsync_dll_element_ *e;
while ((e = nsync_dll_first_(_pthread_list))) {
_pthread_list = nsync_dll_remove_(_pthread_list, e);
_pthread_free(e->container);
}
}
static void _pthread_onfork(int i) { static void _pthread_onfork(int i) {
struct AtFork *a; struct AtFork *a;
struct PosixThread *pt; struct PosixThread *pt;
@ -44,17 +53,11 @@ static void _pthread_onfork(int i) {
_atforks.list = a; _atforks.list = a;
} }
if (i) pthread_spin_unlock(&_atforks.lock); if (i) pthread_spin_unlock(&_atforks.lock);
if (i == 2) {
_pthread_zombies_purge();
if (__tls_enabled) {
pt = (struct PosixThread *)__get_tls()->tib_pthread;
pt->flags |= PT_MAINTHREAD;
}
}
} }
void _pthread_onfork_prepare(void) { void _pthread_onfork_prepare(void) {
_pthread_onfork(0); _pthread_onfork(0);
pthread_spin_lock(&_pthread_lock);
__kmalloc_lock(); __kmalloc_lock();
__mmi_lock(); __mmi_lock();
} }
@ -62,6 +65,7 @@ void _pthread_onfork_prepare(void) {
void _pthread_onfork_parent(void) { void _pthread_onfork_parent(void) {
__mmi_unlock(); __mmi_unlock();
__kmalloc_unlock(); __kmalloc_unlock();
pthread_spin_unlock(&_pthread_lock);
_pthread_onfork(1); _pthread_onfork(1);
} }
@ -72,12 +76,26 @@ void _pthread_onfork_child(void) {
extern pthread_mutex_t __mmi_lock_obj; extern pthread_mutex_t __mmi_lock_obj;
tib = __get_tls(); tib = __get_tls();
pt = (struct PosixThread *)tib->tib_pthread; pt = (struct PosixThread *)tib->tib_pthread;
// let's choose to let the new process live.
// even though it's unclear what to do with this kind of race.
atomic_store_explicit(&pt->cancelled, false, memory_order_relaxed); atomic_store_explicit(&pt->cancelled, false, memory_order_relaxed);
// wipe core runtime locks
pthread_mutexattr_init(&attr); pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&__mmi_lock_obj, &attr); pthread_mutex_init(&__mmi_lock_obj, &attr);
pthread_spin_init(&_pthread_lock, 0);
__kmalloc_unlock(); __kmalloc_unlock();
// call user-supplied forked child callbacks
_pthread_onfork(2); _pthread_onfork(2);
// delete other threads that existed before forking
// this must come after onfork, since it calls free
_pthread_list = nsync_dll_remove_(_pthread_list, &pt->list);
_pthread_purge();
_pthread_list = nsync_dll_make_first_in_list_(_pthread_list, &pt->list);
} }
int _pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) { int _pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) {

View file

@ -307,7 +307,7 @@ errno_t pthread_cancel(pthread_t thread) {
void pthread_testcancel(void) { void pthread_testcancel(void) {
struct PosixThread *pt; struct PosixThread *pt;
if (!__tls_enabled) return; if (!__tls_enabled) return;
pt = (struct PosixThread *)__get_tls()->tib_pthread; if (!(pt = (struct PosixThread *)__get_tls()->tib_pthread)) return;
if (pt->flags & PT_NOCANCEL) return; if (pt->flags & PT_NOCANCEL) return;
if ((!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) && if ((!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) &&
atomic_load_explicit(&pt->cancelled, memory_order_acquire)) { atomic_load_explicit(&pt->cancelled, memory_order_acquire)) {
@ -335,7 +335,7 @@ errno_t pthread_testcancel_np(void) {
int rc; int rc;
struct PosixThread *pt; struct PosixThread *pt;
if (!__tls_enabled) return 0; if (!__tls_enabled) return 0;
pt = (struct PosixThread *)__get_tls()->tib_pthread; if (!(pt = (struct PosixThread *)__get_tls()->tib_pthread)) return 0;
if (pt->flags & PT_NOCANCEL) return 0; if (pt->flags & PT_NOCANCEL) return 0;
if (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return 0; if (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return 0;
if (!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) { if (!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) {

View file

@ -39,7 +39,9 @@
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/spawn.h" #include "libc/thread/spawn.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "libc/thread/wait0.internal.h" #include "libc/thread/wait0.internal.h"
#include "third_party/nsync/dll.h"
STATIC_YOINK("nsync_mu_lock"); STATIC_YOINK("nsync_mu_lock");
STATIC_YOINK("nsync_mu_unlock"); STATIC_YOINK("nsync_mu_unlock");
@ -48,19 +50,13 @@ STATIC_YOINK("_pthread_atfork");
#define MAP_ANON_OPENBSD 0x1000 #define MAP_ANON_OPENBSD 0x1000
#define MAP_STACK_OPENBSD 0x4000 #define MAP_STACK_OPENBSD 0x4000
errno_t _pthread_wait(struct PosixThread *pt) {
return _wait0(&pt->tib->tib_tid);
}
void _pthread_free(struct PosixThread *pt) { void _pthread_free(struct PosixThread *pt) {
if (pt->flags & PT_MAINTHREAD) return; if (pt->flags & PT_STATIC) return;
free(pt->tls); free(pt->tls);
if ((pt->flags & 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)) { _npassert(!munmap(pt->attr.__stackaddr, pt->attr.__stacksize));
notpossible;
}
} }
if (pt->altstack) { if (pt->altstack) {
free(pt->altstack); free(pt->altstack);
@ -69,9 +65,11 @@ void _pthread_free(struct PosixThread *pt) {
} }
static int PosixThread(void *arg, int tid) { static int PosixThread(void *arg, int tid) {
void *rc;
struct sigaltstack ss;
struct PosixThread *pt = arg; struct PosixThread *pt = arg;
enum PosixThreadStatus status; enum PosixThreadStatus status;
struct sigaltstack ss; _unassert(__get_tls()->tib_tid > 0);
if (pt->altstack) { if (pt->altstack) {
ss.ss_flags = 0; ss.ss_flags = 0;
ss.ss_size = SIGSTKSZ; ss.ss_size = SIGSTKSZ;
@ -87,12 +85,12 @@ static int PosixThread(void *arg, int tid) {
if (!setjmp(pt->exiter)) { if (!setjmp(pt->exiter)) {
__get_tls()->tib_pthread = (pthread_t)pt; __get_tls()->tib_pthread = (pthread_t)pt;
_sigsetmask(pt->sigmask); _sigsetmask(pt->sigmask);
pt->rc = pt->start(pt->arg); rc = pt->start(pt->arg);
// ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup // ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup
_npassert(!pt->cleanup); _npassert(!pt->cleanup);
// calling pthread_exit() will either jump back here, or call exit
pthread_exit(rc);
} }
// 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 to clone polyfill which clears tid, wakes futex, and exits
return 0; return 0;
} }
@ -234,13 +232,19 @@ static errno_t pthread_create_impl(pthread_t *thread,
case PTHREAD_CREATE_DETACHED: case PTHREAD_CREATE_DETACHED:
atomic_store_explicit(&pt->status, kPosixThreadDetached, atomic_store_explicit(&pt->status, kPosixThreadDetached,
memory_order_relaxed); memory_order_relaxed);
_pthread_zombies_add(pt);
break; break;
default: default:
_pthread_free(pt); _pthread_free(pt);
return EINVAL; return EINVAL;
} }
// add thread to global list
// we add it to the end since zombies go at the beginning
nsync_dll_init_(&pt->list, pt);
pthread_spin_lock(&_pthread_lock);
_pthread_list = nsync_dll_make_last_in_list_(_pthread_list, &pt->list);
pthread_spin_unlock(&_pthread_lock);
// launch PosixThread(pt) in new thread // launch PosixThread(pt) in new thread
pt->sigmask = oldsigs; pt->sigmask = oldsigs;
if ((rc = clone(PosixThread, pt->attr.__stackaddr, if ((rc = clone(PosixThread, pt->attr.__stackaddr,
@ -249,6 +253,9 @@ static errno_t pthread_create_impl(pthread_t *thread,
CLONE_SIGHAND | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_SIGHAND | CLONE_SETTLS | CLONE_PARENT_SETTID |
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID, CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
pt, &pt->ptid, pt->tib, &pt->tib->tib_tid))) { pt, &pt->ptid, pt->tib, &pt->tib->tib_tid))) {
pthread_spin_lock(&_pthread_lock);
_pthread_list = nsync_dll_remove_(_pthread_list, &pt->list);
pthread_spin_unlock(&_pthread_lock);
_pthread_free(pt); _pthread_free(pt);
return rc; return rc;
} }
@ -313,7 +320,7 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg) { void *(*start_routine)(void *), void *arg) {
errno_t rc; errno_t rc;
__require_tls(); __require_tls();
_pthread_zombies_decimate(); pthread_decimate_np();
BLOCK_SIGNALS; BLOCK_SIGNALS;
rc = pthread_create_impl(thread, attr, start_routine, arg, _SigMask); rc = pthread_create_impl(thread, attr, start_routine, arg, _SigMask);
ALLOW_SIGNALS; ALLOW_SIGNALS;

View file

@ -17,68 +17,33 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.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 "third_party/nsync/dll.h"
// TODO(jart): track all threads, not just zombies /**
* Releases memory of detached threads that have terminated.
static struct Zombie { */
struct Zombie *next; void pthread_decimate_np(void) {
nsync_dll_element_ *e;
struct PosixThread *pt; struct PosixThread *pt;
} * _pthread_zombies; enum PosixThreadStatus status;
StartOver:
void _pthread_zombies_add(struct PosixThread *pt) { pthread_spin_lock(&_pthread_lock);
struct Zombie *z; for (e = nsync_dll_first_(_pthread_list); e;
if ((z = malloc(sizeof(struct Zombie)))) { e = nsync_dll_next_(_pthread_list, e)) {
z->pt = pt; pt = (struct PosixThread *)e->container;
z->next = atomic_load(&_pthread_zombies); if (pt->tib == __get_tls()) continue;
for (;;) { status = atomic_load_explicit(&pt->status, memory_order_acquire);
if (atomic_compare_exchange_weak(&_pthread_zombies, &z->next, z)) { if (status != kPosixThreadZombie) break;
break; if (!atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire)) {
} _pthread_list = nsync_dll_remove_(_pthread_list, e);
pthread_spin_unlock(&_pthread_lock);
_pthread_free(pt);
goto StartOver;
} }
} }
} pthread_spin_unlock(&_pthread_lock);
static void _pthread_zombies_collect(struct Zombie *z) {
// TODO(jart): We need a trywait() op here to avoid cancellation.
_pthread_wait(z->pt);
_pthread_free(z->pt);
free(z);
}
void _pthread_zombies_decimate(void) {
struct Zombie *z;
while ((z = atomic_load_explicit(&_pthread_zombies, memory_order_relaxed)) &&
atomic_load(&z->pt->status) == kPosixThreadZombie) {
if (atomic_compare_exchange_weak(&_pthread_zombies, &z, z->next)) {
_pthread_zombies_collect(z);
}
}
}
void _pthread_zombies_harvest(void) {
struct Zombie *z;
while ((z = atomic_load_explicit(&_pthread_zombies, memory_order_relaxed))) {
if (atomic_compare_exchange_weak(&_pthread_zombies, &z, z->next)) {
_pthread_zombies_collect(z);
}
}
}
void _pthread_zombies_purge(void) {
struct Zombie *z, *n;
while ((z = atomic_load_explicit(&_pthread_zombies, memory_order_relaxed))) {
_pthread_free(z->pt);
n = z->next;
free(z);
atomic_store_explicit(&_pthread_zombies, n, memory_order_relaxed);
}
}
__attribute__((__constructor__)) static void _pthread_zombies_init(void) {
atexit(_pthread_zombies_harvest);
} }

View file

@ -16,45 +16,52 @@
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/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h" #include "libc/macros.internal.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/spawn.h" #include "libc/thread/spawn.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/dll.h"
/** /**
* Asks POSIX thread to free itself automatically on termination. * Asks POSIX thread to free itself automatically upon termination.
*
* If this function is used, then it's important to use pthread_exit()
* rather than exit() since otherwise your program isn't guaranteed to
* gracefully terminate.
*
* Detaching a non-joinable thread is undefined behavior. For example,
* pthread_detach() can't be called twice on the same thread.
* *
* @return 0 on success, or errno with error * @return 0 on success, or errno with error
* @raise EINVAL if thread is null or already detached
* @returnserrno * @returnserrno
* @threadsafe * @threadsafe
*/ */
errno_t pthread_detach(pthread_t thread) { errno_t pthread_detach(pthread_t thread) {
struct PosixThread *pt; struct PosixThread *pt;
enum PosixThreadStatus status, transition; enum PosixThreadStatus status, transition;
if (!(pt = (struct PosixThread *)thread)) return EINVAL; for (pt = (struct PosixThread *)thread;;) {
for (;;) {
status = atomic_load_explicit(&pt->status, memory_order_acquire); status = atomic_load_explicit(&pt->status, memory_order_acquire);
if (status == kPosixThreadDetached || status == kPosixThreadZombie) { if (status == kPosixThreadJoinable) {
// these two states indicate the thread was already detached, in
// which case it's already listed under _pthread_zombies.
return EINVAL;
} else if (status == kPosixThreadJoinable) {
transition = kPosixThreadDetached; transition = kPosixThreadDetached;
} else if (status == kPosixThreadTerminated) { } else if (status == kPosixThreadTerminated) {
transition = kPosixThreadZombie; transition = kPosixThreadZombie;
} else { } else {
notpossible; unreachable;
} }
if (atomic_compare_exchange_weak_explicit(&pt->status, &status, transition, if (atomic_compare_exchange_weak_explicit(&pt->status, &status, transition,
memory_order_release, memory_order_release,
memory_order_relaxed)) { memory_order_relaxed)) {
_pthread_zombies_add(pt);
break; break;
} }
} }
if (transition == kPosixThreadZombie) {
_pthread_zombify(pt);
}
pthread_decimate_np();
return 0; return 0;
} }

View file

@ -16,16 +16,52 @@
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/dce.h" #include "libc/dce.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/limits.h" #include "libc/limits.h"
#include "libc/mem/gc.h" #include "libc/mem/gc.h"
#include "libc/runtime/runtime.h" #include "libc/runtime/runtime.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
#include "third_party/nsync/dll.h"
#include "third_party/nsync/futex.internal.h" #include "third_party/nsync/futex.internal.h"
static void CleanupThread(struct PosixThread *pt) {
struct _pthread_cleanup_buffer *cb;
while ((cb = pt->cleanup)) {
pt->cleanup = cb->__prev;
cb->__routine(cb->__arg);
}
}
static void DestroyTlsKeys(struct CosmoTib *tib) {
int i, j, gotsome;
void *val, **keys;
pthread_key_dtor dtor;
keys = tib->tib_keys;
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; ++j) {
for (gotsome = i = 0; i < PTHREAD_KEYS_MAX; ++i) {
if ((val = keys[i]) &&
(dtor = atomic_load_explicit(_pthread_key_dtor + i,
memory_order_relaxed)) &&
dtor != (pthread_key_dtor)-1) {
gotsome = 1;
keys[i] = 0;
dtor(val);
}
}
if (!gotsome) {
break;
}
}
}
/** /**
* Terminates current POSIX thread. * Terminates current POSIX thread.
* *
@ -60,35 +96,59 @@
* @noreturn * @noreturn
*/ */
wontreturn void pthread_exit(void *rc) { wontreturn void pthread_exit(void *rc) {
struct CosmoTib *tib;
struct PosixThread *pt; struct PosixThread *pt;
struct _pthread_cleanup_buffer *cb; enum PosixThreadStatus status, transition;
pt = (struct PosixThread *)__get_tls()->tib_pthread;
STRACE("pthread_exit(%p)", rc);
tib = __get_tls();
pt = (struct PosixThread *)tib->tib_pthread;
_unassert(~pt->flags & PT_EXITING);
pt->flags |= PT_EXITING;
pt->rc = rc; pt->rc = rc;
// the memory of pthread cleanup objects lives on the stack
// so we need to harvest them before calling longjmp() // free resources
while ((cb = pt->cleanup)) { CleanupThread(pt);
pt->cleanup = cb->__prev; DestroyTlsKeys(tib);
cb->__routine(cb->__arg); _pthread_ungarbage();
pthread_decimate_np();
// transition the thread to a terminated state
status = atomic_load_explicit(&pt->status, memory_order_acquire);
do {
switch (status) {
case kPosixThreadJoinable:
transition = kPosixThreadTerminated;
break;
case kPosixThreadDetached:
transition = kPosixThreadZombie;
break;
default:
unreachable;
}
} while (!atomic_compare_exchange_weak_explicit(
&pt->status, &status, transition, memory_order_release,
memory_order_relaxed));
// make this thread a zombie if it was detached
if (transition == kPosixThreadZombie) {
_pthread_zombify(pt);
} }
// TODO(jart): An orphaned thread should become the main thread.
// TODO(jart): This should call __cxa_finalize() for the orphan. // check if this is the main thread or an orphaned thread
if (~pt->flags & PT_MAINTHREAD) { if (pthread_orphan_np()) {
// this thread was created by pthread_create() exit(0);
// 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. // check if the main thread has died whilst children live
longjmp(pt->exiter, 1); // note that the main thread is joinable by child threads
} else { if (pt->flags & PT_STATIC) {
// this is the main thread atomic_store_explicit(&tib->tib_tid, 0, memory_order_release);
// release as much resources and possible and mark it terminated nsync_futex_wake_(&tib->tib_tid, INT_MAX, !IsWindows());
_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.
atomic_store_explicit(&__get_tls()->tib_tid, 0, memory_order_release);
nsync_futex_wake_(&__get_tls()->tib_tid, INT_MAX, !IsWindows());
_Exit1(0); _Exit1(0);
} }
// this is a child thread
longjmp(pt->exiter, 1);
} }

View file

@ -16,21 +16,29 @@
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/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "libc/thread/tls.h"
#include "libc/thread/wait0.internal.h"
/** /**
* Waits for thread to terminate. * Waits for thread to terminate.
* *
* Multiple threads joining the same thread is undefined behavior. If a
* deferred or masked cancellation happens to the calling thread either
* before or during the waiting process then the target thread will not
* be joined. Calling pthread_join() on a non-joinable thread, e.g. one
* that's been detached, is undefined behavior. If a thread attempts to
* join itself, then the behavior is undefined.
*
* @param value_ptr if non-null will receive pthread_exit() argument * @param value_ptr if non-null will receive pthread_exit() argument
* if the thread called pthread_exit(), or `PTHREAD_CANCELED` if * if the thread called pthread_exit(), or `PTHREAD_CANCELED` if
* pthread_cancel() destroyed the thread instead * pthread_cancel() destroyed the thread instead
* @return 0 on success, or errno on error * @return 0 on success, or errno on error
* @raise ECANCELED if calling thread was cancelled in masked mode * @raise ECANCELED if calling thread was cancelled in masked mode
* @raise EDEADLK if `thread` is the current thread
* @raise EINVAL if `thread` is detached
* @cancellationpoint * @cancellationpoint
* @returnserrno * @returnserrno
* @threadsafe * @threadsafe
@ -38,20 +46,22 @@
errno_t pthread_join(pthread_t thread, void **value_ptr) { errno_t pthread_join(pthread_t thread, void **value_ptr) {
errno_t rc; errno_t rc;
struct PosixThread *pt; struct PosixThread *pt;
if (thread == __get_tls()->tib_pthread) { enum PosixThreadStatus status;
return EDEADLK; pt = (struct PosixThread *)thread;
status = atomic_load_explicit(&pt->status, memory_order_acquire);
// "The behavior is undefined if the value specified by the thread
// argument to pthread_join() does not refer to a joinable thread."
// ──Quoth POSIX.1-2017
_unassert(status == kPosixThreadJoinable || status == kPosixThreadTerminated);
if (!(rc = _wait0(&pt->tib->tib_tid))) {
pthread_spin_lock(&_pthread_lock);
_pthread_list = nsync_dll_remove_(_pthread_list, &pt->list);
pthread_spin_unlock(&_pthread_lock);
if (value_ptr) {
*value_ptr = pt->rc;
}
_pthread_free(pt);
pthread_decimate_np();
} }
if (!(pt = (struct PosixThread *)thread) || //
pt->status == kPosixThreadZombie || //
pt->status == kPosixThreadDetached) {
return EINVAL;
}
if ((rc = _pthread_wait(pt))) {
return rc;
}
if (value_ptr) {
*value_ptr = pt->rc;
}
_pthread_free(pt);
return 0; return 0;
} }

View file

@ -16,23 +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/intrin/atomic.h"
#include "libc/intrin/weaken.h"
#include "libc/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
void _pthread_cleanup(struct PosixThread *pt) { /**
_pthread_ungarbage(); * Returns true if calling thread is the only thread.
if (_weaken(_pthread_key_destruct)) { */
_weaken(_pthread_key_destruct)(); bool pthread_orphan_np(void) {
} bool res;
if (atomic_load_explicit(&pt->status, memory_order_acquire) == pthread_spin_lock(&_pthread_lock);
kPosixThreadDetached) { res = _pthread_list == _pthread_list->prev &&
atomic_store_explicit(&pt->status, kPosixThreadZombie, _pthread_list == _pthread_list->next;
memory_order_release); pthread_spin_unlock(&_pthread_lock);
} else { return res;
atomic_store_explicit(&pt->status, kPosixThreadTerminated,
memory_order_release);
}
} }

View file

@ -0,0 +1,91 @@
/*-*- 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/blockcancel.internal.h"
#include "libc/calls/calls.h"
#include "libc/intrin/kprintf.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "third_party/nsync/dll.h"
#define N 2048
#define M 15
#define append(f, ...) o += f(buf + o, N - o, __VA_ARGS__)
static const char *DescribeStatus(enum PosixThreadStatus status) {
switch (status) {
case kPosixThreadJoinable:
return "JOINAB";
case kPosixThreadDetached:
return "DETACH";
case kPosixThreadTerminated:
return "TERMIN";
case kPosixThreadZombie:
return "ZOMBIE";
default:
unreachable;
}
}
static const char *DescribeFlags(char buf[M], struct PosixThread *pt) {
char *p = buf;
if (pt->cancelled) *p++ = '*';
if (pt->flags & PT_EXITING) *p++ = 'X';
if (pt->flags & PT_STATIC) *p++ = 'S';
if (pt->flags & PT_OWNSTACK) *p++ = 'O';
if (pt->flags & PT_ASYNC) *p++ = 'A';
if (pt->flags & PT_MASKED) *p++ = 'M';
if (pt->flags & PT_OPENBSD_KLUDGE) *p++ = 'K';
if (pt->flags & PT_INCANCEL) *p++ = '?';
if (pt->flags & PT_NOCANCEL) *p++ = '!';
*p = 0;
return buf;
}
int pthread_print_np(int fd, const char *fmt, ...) {
va_list va;
int rc, o = 0;
nsync_dll_element_ *e;
struct PosixThread *pt;
char buf[N], flagbuf[M];
pthread_spin_lock(&_pthread_lock);
if (fmt) {
va_start(va, fmt);
append(kvsnprintf, fmt, va);
va_end(va);
append(ksnprintf, "\n");
}
append(ksnprintf, "%6s %6s %6s %6s %s\n", "ptid", "tid", "status", "flags",
"start");
for (e = nsync_dll_first_(_pthread_list); e;
e = nsync_dll_next_(_pthread_list, e)) {
pt = (struct PosixThread *)e->container;
append(ksnprintf, "%-6d %-6d %6s %6s %t\n", pt->ptid, pt->tib->tib_tid,
DescribeStatus(pt->status), DescribeFlags(flagbuf, pt), pt->start);
}
pthread_spin_unlock(&_pthread_lock);
BLOCK_CANCELLATIONS;
strace_enabled(-1);
rc = write(fd, buf, strlen(buf));
strace_enabled(+1);
ALLOW_CANCELLATIONS;
return rc;
}

View file

@ -23,9 +23,12 @@
void _pthread_ungarbage(void) { void _pthread_ungarbage(void) {
int i; int i;
struct Garbages *g; struct Garbages *g;
if ((g = __get_tls()->tib_garbages)) { struct CosmoTib *tib;
for (i = g->i; i--;) { tib = __get_tls();
((void (*)(intptr_t))g->p[i].fn)(g->p[i].arg); while ((g = tib->tib_garbages)) {
tib->tib_garbages = 0;
while (g->i--) {
((void (*)(intptr_t))g->p[g->i].fn)(g->p[g->i].arg);
} }
free(g->p); free(g->p);
free(g); free(g);

View file

@ -16,30 +16,13 @@
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/thread/posixthread.internal.h" #include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "libc/thread/tls.h" #include "third_party/nsync/dll.h"
void _pthread_key_destruct(void) { void _pthread_zombify(struct PosixThread *pt) {
int i, j, gotsome; pthread_spin_lock(&_pthread_lock);
void *val, **keys; _pthread_list = nsync_dll_remove_(_pthread_list, &pt->list);
pthread_key_dtor dtor; _pthread_list = nsync_dll_make_first_in_list_(_pthread_list, &pt->list);
if (!__tls_enabled) return; pthread_spin_unlock(&_pthread_lock);
keys = __get_tls()->tib_keys;
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; ++j) {
for (gotsome = i = 0; i < PTHREAD_KEYS_MAX; ++i) {
if ((val = keys[i]) &&
(dtor = atomic_load_explicit(_pthread_key_dtor + i,
memory_order_relaxed)) &&
dtor != (pthread_key_dtor)-1) {
gotsome = 1;
keys[i] = 0;
dtor(val);
}
}
if (!gotsome) {
break;
}
}
} }

View file

@ -110,10 +110,13 @@ int pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *),
void *); void *);
int pthread_yield(void); int pthread_yield(void);
bool pthread_orphan_np(void);
void pthread_testcancel(void); void pthread_testcancel(void);
void pthread_decimate_np(void);
int pthread_testcancel_np(void); int pthread_testcancel_np(void);
void pthread_exit(void *) wontreturn; void pthread_exit(void *) wontreturn;
pthread_t pthread_self(void) pureconst; pthread_t pthread_self(void) pureconst;
int pthread_print_np(int, const char *, ...);
pthread_id_np_t pthread_getthreadid_np(void); pthread_id_np_t pthread_getthreadid_np(void);
int pthread_getunique_np(pthread_t, pthread_id_np_t *); int pthread_getunique_np(pthread_t, pthread_id_np_t *);
int pthread_setname_np(pthread_t, const char *); int pthread_setname_np(pthread_t, const char *);
@ -208,7 +211,7 @@ void _pthread_cleanup_push(struct _pthread_cleanup_buffer *, void (*)(void *),
} }
#if (__GNUC__ + 0) * 100 + (__GNUC_MINOR__ + 0) >= 407 && \ #if (__GNUC__ + 0) * 100 + (__GNUC_MINOR__ + 0) >= 407 && \
!defined(__STRICT_ANSI__) !defined(__STRICT_ANSI__) && !defined(MODE_DBG)
extern const errno_t EBUSY; extern const errno_t EBUSY;
#define pthread_spin_lock(pSpin) \ #define pthread_spin_lock(pSpin) \
({ \ ({ \

View file

@ -45,18 +45,17 @@ extern unsigned __tls_index;
void __require_tls(void); void __require_tls(void);
void __set_tls(struct CosmoTib *); void __set_tls(struct CosmoTib *);
#if defined(__GNUC__) && defined(__x86_64__) && !defined(__STRICT_ANSI__)
/** /**
* Returns location of thread information block. * Returns location of thread information block.
* *
* This can't be used in privileged functions. * This can't be used in privileged functions.
*/ */
static inline struct CosmoTib *__get_tls(void) { #define __get_tls() \
struct CosmoTib *_tib; ({ \
asm("mov\t%%fs:0,%0" : "=r"(_tib) : /* no inputs */ : "memory"); struct CosmoTib *_t; \
return _tib; asm("mov\t%%fs:0,%0" : "=r"(_t) : /* no inputs */ : "memory"); \
} _t; \
#endif /* GNU x86-64 */ })
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -16,10 +16,13 @@
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/calls/cp.internal.h" #include "libc/calls/cp.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/atomic.h" #include "libc/intrin/atomic.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
#include "libc/thread/wait0.internal.h" #include "libc/thread/wait0.internal.h"
#include "third_party/nsync/futex.internal.h" #include "third_party/nsync/futex.internal.h"
@ -38,13 +41,21 @@
*/ */
errno_t _wait0(const atomic_int *ctid) { errno_t _wait0(const atomic_int *ctid) {
int x, rc = 0; int x, rc = 0;
BEGIN_CANCELLATION_POINT; // "The behavior is undefined if the value specified by the thread
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { // argument to pthread_join() refers to the calling thread."
if (nsync_futex_wait_(ctid, x, !IsWindows(), 0) == -ECANCELED) { // ──Quoth POSIX.1-2017
rc = ECANCELED; _unassert(ctid != &__get_tls()->tib_tid);
break; // "If the thread calling pthread_join() is canceled, then the target
// thread shall not be detached." ──Quoth POSIX.1-2017
if (!(rc = pthread_testcancel_np())) {
BEGIN_CANCELLATION_POINT;
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
if (nsync_futex_wait_(ctid, x, !IsWindows(), 0) == -ECANCELED) {
rc = ECANCELED;
break;
}
} }
END_CANCELLATION_POINT;
} }
END_CANCELLATION_POINT;
return rc; return rc;
} }

View file

@ -116,7 +116,9 @@ static int __zipos_mkfd(int minfd) {
static int __zipos_setfd(int fd, struct ZiposHandle *h, unsigned flags, static int __zipos_setfd(int fd, struct ZiposHandle *h, unsigned flags,
int mode) { int mode) {
_cmpxchg(&g_fds.f, fd, fd + 1); int want = fd;
atomic_compare_exchange_strong_explicit(
&g_fds.f, &want, fd + 1, memory_order_release, memory_order_relaxed);
g_fds.p[fd].kind = kFdZip; g_fds.p[fd].kind = kFdZip;
g_fds.p[fd].handle = (intptr_t)h; g_fds.p[fd].handle = (intptr_t)h;
g_fds.p[fd].flags = flags | O_CLOEXEC; g_fds.p[fd].flags = flags | O_CLOEXEC;

View file

@ -54,16 +54,11 @@ void TriggerSignal(void) {
} }
static void *Increment(void *arg) { static void *Increment(void *arg) {
ASSERT_EQ(EDEADLK, pthread_join(pthread_self(), 0));
ASSERT_EQ(gettid(), pthread_getthreadid_np()); ASSERT_EQ(gettid(), pthread_getthreadid_np());
TriggerSignal(); TriggerSignal();
return (void *)((uintptr_t)arg + 1); return (void *)((uintptr_t)arg + 1);
} }
TEST(pthread_create, joinSelfDeadlocks) {
ASSERT_EQ(EDEADLK, pthread_join(pthread_self(), 0));
}
TEST(pthread_create, testCreateReturnJoin) { TEST(pthread_create, testCreateReturnJoin) {
void *rc; void *rc;
pthread_t id; pthread_t id;
@ -279,4 +274,7 @@ BENCH(pthread_create, bench) {
EZBENCH2("CreateJoin", donothing, CreateJoin()); EZBENCH2("CreateJoin", donothing, CreateJoin());
EZBENCH2("CreateDetach", donothing, CreateDetach()); EZBENCH2("CreateDetach", donothing, CreateDetach());
EZBENCH2("CreateDetached", donothing, CreateDetached()); EZBENCH2("CreateDetached", donothing, CreateDetached());
while (!pthread_orphan_np()) {
pthread_decimate_np();
}
} }

View file

@ -19,10 +19,13 @@
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigaction.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/sig.h"
#include "libc/testlib/testlib.h" #include "libc/testlib/testlib.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
#include "third_party/nsync/dll.h"
void OnUsr1(int sig, struct siginfo *si, void *vctx) { void OnUsr1(int sig, struct siginfo *si, void *vctx) {
struct ucontext *ctx = vctx; struct ucontext *ctx = vctx;
@ -40,7 +43,6 @@ void TriggerSignal(void) {
} }
static void *Increment(void *arg) { static void *Increment(void *arg) {
ASSERT_EQ(EDEADLK, pthread_join(pthread_self(), 0));
ASSERT_EQ(gettid(), pthread_getthreadid_np()); ASSERT_EQ(gettid(), pthread_getthreadid_np());
TriggerSignal(); TriggerSignal();
return (void *)((uintptr_t)arg + 1); return (void *)((uintptr_t)arg + 1);
@ -50,6 +52,9 @@ TEST(pthread_detach, testCreateReturn) {
pthread_t id; pthread_t id;
ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0)); ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0));
ASSERT_EQ(0, pthread_detach(id)); ASSERT_EQ(0, pthread_detach(id));
while (!pthread_orphan_np()) {
pthread_decimate_np();
}
} }
TEST(pthread_detach, testDetachUponCreation) { TEST(pthread_detach, testDetachUponCreation) {
@ -58,4 +63,7 @@ TEST(pthread_detach, testDetachUponCreation) {
ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
ASSERT_EQ(0, pthread_create(0, &attr, Increment, 0)); ASSERT_EQ(0, pthread_create(0, &attr, Increment, 0));
ASSERT_EQ(0, pthread_attr_destroy(&attr)); ASSERT_EQ(0, pthread_attr_destroy(&attr));
while (!pthread_orphan_np()) {
pthread_decimate_np();
}
} }

View file

@ -0,0 +1,66 @@
/*-*- 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/runtime/runtime.h"
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/thread.h"
#define MAIN 150
#define CHILD 170
pthread_t main_thread;
_Thread_local static int exit_code;
void OnExit(void) {
_Exit(exit_code);
}
void *Worker(void *arg) {
ASSERT_EQ(0, pthread_join(main_thread, 0));
exit_code = CHILD;
pthread_exit(0);
}
TEST(pthread_exit, joinableOrphanedChild_runsAtexitHandlers) {
pthread_attr_t attr;
SPAWN(fork);
atexit(OnExit);
exit_code = MAIN;
main_thread = pthread_self();
ASSERT_EQ(0, pthread_attr_init(&attr));
ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE));
ASSERT_EQ(0, pthread_create(0, &attr, Worker, 0));
ASSERT_EQ(0, pthread_attr_destroy(&attr));
pthread_exit(0);
EXITS(CHILD);
}
TEST(pthread_exit, detachedOrphanedChild_runsAtexitHandlers) {
pthread_attr_t attr;
SPAWN(fork);
atexit(OnExit);
exit_code = MAIN;
main_thread = pthread_self();
ASSERT_EQ(0, pthread_attr_init(&attr));
ASSERT_EQ(0, pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
ASSERT_EQ(0, pthread_create(0, &attr, Worker, 0));
ASSERT_EQ(0, pthread_attr_destroy(&attr));
pthread_exit(0);
EXITS(CHILD);
}

View file

@ -16,23 +16,56 @@
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/intrin/kprintf.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/nexgen32e/nexgen32e.h"
#include "libc/testlib/testlib.h" #include "libc/testlib/testlib.h"
#include "libc/thread/thread.h" #include "libc/thread/thread.h"
TEST(pthread_key_create, testRunsDtors_becauseNoLeakReport) { pthread_key_t mykey;
void *x; int destructor_calls;
pthread_key_t key;
x = malloc(123); void KeyDestructor(void *arg) {
EXPECT_EQ(0, pthread_key_create(&key, free)); EXPECT_EQ(0, pthread_getspecific(mykey));
EXPECT_EQ(0, pthread_setspecific(key, x)); ASSERT_EQ(31337, (intptr_t)arg);
EXPECT_EQ(x, pthread_getspecific(key)); ++destructor_calls;
x = malloc(123); }
EXPECT_EQ(0, pthread_key_create(&key, free));
EXPECT_EQ(0, pthread_setspecific(key, x)); void *Worker(void *arg) {
EXPECT_EQ(x, pthread_getspecific(key)); ASSERT_EQ(0, pthread_getspecific(mykey));
x = malloc(123); ASSERT_EQ(0, pthread_setspecific(mykey, (void *)31337));
EXPECT_EQ(0, pthread_key_create(&key, free)); ASSERT_EQ((void *)31337, pthread_getspecific(mykey));
EXPECT_EQ(0, pthread_setspecific(key, x)); return 0;
EXPECT_EQ(x, pthread_getspecific(key)); }
TEST(pthread_key_create, test) {
pthread_t th;
destructor_calls = 0;
ASSERT_EQ(0, pthread_key_create(&mykey, KeyDestructor));
ASSERT_EQ(0, pthread_setspecific(mykey, (void *)666));
ASSERT_EQ(0, pthread_create(&th, 0, Worker, 0));
ASSERT_EQ(0, pthread_join(th, 0));
ASSERT_EQ(1, destructor_calls);
ASSERT_EQ((void *)666, pthread_getspecific(mykey));
ASSERT_EQ(0, pthread_key_delete(mykey));
}
void KeyDestructorCraze(void *arg) {
EXPECT_EQ(0, pthread_getspecific(mykey));
ASSERT_EQ(31337, (intptr_t)arg);
EXPECT_EQ(0, pthread_setspecific(mykey, (void *)31337));
++destructor_calls;
}
TEST(pthread_key_create, destructorKeepsSettingKey_willHalt) {
pthread_t th;
destructor_calls = 0;
ASSERT_EQ(0, pthread_key_create(&mykey, KeyDestructorCraze));
ASSERT_EQ(0, pthread_setspecific(mykey, (void *)666));
ASSERT_EQ(0, pthread_create(&th, 0, Worker, 0));
ASSERT_EQ(0, pthread_join(th, 0));
ASSERT_EQ(PTHREAD_DESTRUCTOR_ITERATIONS, destructor_calls);
ASSERT_EQ((void *)666, pthread_getspecific(mykey));
ASSERT_EQ(0, pthread_key_delete(mykey));
} }