mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-02-07 15:03:34 +00:00
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:
parent
b74d8c1acd
commit
cee6871710
37 changed files with 638 additions and 314 deletions
|
@ -953,7 +953,7 @@ static void *__asan_morgue_add(void *p) {
|
|||
p, memory_order_acq_rel);
|
||||
}
|
||||
|
||||
static void __asan_morgue_flush(void) {
|
||||
__attribute__((__destructor__)) static void __asan_morgue_flush(void) {
|
||||
unsigned i;
|
||||
for (i = 0; i < ARRAYLEN(__asan_morgue.p); ++i) {
|
||||
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);
|
||||
}
|
||||
|
||||
textstartup void __asan_init(int argc, char **argv, char **envp,
|
||||
intptr_t *auxv) {
|
||||
__attribute__((__constructor__)) void __asan_init(int argc, char **argv,
|
||||
char **envp, intptr_t *auxv) {
|
||||
static bool once;
|
||||
if (!_cmpxchg(&once, false, true)) return;
|
||||
if (IsWindows() && NtGetVersion() < kNtVersionWindows10) {
|
||||
|
@ -1497,13 +1497,3 @@ textstartup void __asan_init(int argc, char **argv, char **envp,
|
|||
STRACE("/_/ \\_\\____/_/ \\_\\_| \\_|");
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/dce.h"
|
||||
#include "libc/intrin/asmflag.h"
|
||||
#include "libc/intrin/promises.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/nt/thread.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/sysv/consts/nr.h"
|
||||
|
@ -47,7 +45,6 @@ __msabi extern typeof(ExitThread) *const __imp_ExitThread;
|
|||
privileged wontreturn void _Exit1(int rc) {
|
||||
char cf;
|
||||
int ax, dx, di, si;
|
||||
STRACE("_Exit1(%d)", rc);
|
||||
if (!IsWindows() && !IsMetal()) {
|
||||
// exit() on Linux
|
||||
// thr_exit() on FreeBSD
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/state.internal.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/extend.internal.h"
|
||||
#include "libc/intrin/pushpop.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;
|
||||
fds->p[i].kind = pushpop(kFdFile);
|
||||
fds->p[i].handle = h;
|
||||
fds->f = i + 1;
|
||||
atomic_store_explicit(&fds->f, i + 1, memory_order_relaxed);
|
||||
}
|
||||
|
||||
textstartup void InitializeFileDescriptors(void) {
|
||||
|
@ -49,7 +50,7 @@ textstartup void InitializeFileDescriptors(void) {
|
|||
fds = VEIL("r", &g_fds);
|
||||
fds->p = fds->e = (void *)kMemtrackFdsStart;
|
||||
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,
|
||||
kMemtrackFdsStart + kMemtrackFdsSize);
|
||||
if (IsMetal()) {
|
||||
|
|
|
@ -16,16 +16,26 @@
|
|||
│ 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/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (0 <= key && key < PTHREAD_KEYS_MAX) {
|
||||
return __get_tls()->tib_keys[key];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
void *pthread_getspecific(pthread_key_t k) {
|
||||
// "The effect of calling pthread_getspecific() or
|
||||
// pthread_setspecific() with a key value not obtained from
|
||||
// pthread_key_create() or after key has been deleted with
|
||||
// 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];
|
||||
}
|
||||
|
|
|
@ -18,18 +18,24 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
/**
|
||||
* Allocates TLS slot.
|
||||
*
|
||||
* If `dtor` is non-null, then it'll be called upon thread exit when the
|
||||
* key's value is nonzero. The key's value is set to zero before it gets
|
||||
* called. The ordering for multiple destructor calls is unspecified.
|
||||
* This function creates a thread-local storage registration, that will
|
||||
* apply to all threads. The new identifier is written to `key`, and it
|
||||
* 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 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;
|
||||
for (i = 0; i < PTHREAD_KEYS_MAX; ++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,
|
||||
dtor, memory_order_relaxed,
|
||||
dtor, memory_order_release,
|
||||
memory_order_relaxed)) {
|
||||
*key = i;
|
||||
return 0;
|
||||
|
@ -52,7 +58,3 @@ int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) {
|
|||
}
|
||||
return EAGAIN;
|
||||
}
|
||||
|
||||
__attribute__((__constructor__)) static textstartup void _pthread_key_init() {
|
||||
atexit(_pthread_key_destruct);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
|
@ -24,16 +25,18 @@
|
|||
/**
|
||||
* 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()
|
||||
* @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;
|
||||
if (key < PTHREAD_KEYS_MAX) {
|
||||
atomic_store_explicit(_pthread_key_dtor + key, 0, memory_order_relaxed);
|
||||
return 0;
|
||||
} else {
|
||||
return EINVAL;
|
||||
}
|
||||
_unassert(0 <= k && k < PTHREAD_KEYS_MAX);
|
||||
_unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
|
||||
atomic_store_explicit(_pthread_key_dtor + k, 0, memory_order_release);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -16,18 +16,27 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ 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/tls.h"
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (0 <= key && key < PTHREAD_KEYS_MAX) {
|
||||
__get_tls()->tib_keys[key] = val;
|
||||
return 0;
|
||||
} else {
|
||||
return EINVAL;
|
||||
}
|
||||
int pthread_setspecific(pthread_key_t k, const void *val) {
|
||||
// "The effect of calling pthread_getspecific() or
|
||||
// pthread_setspecific() with a key value not obtained from
|
||||
// pthread_key_create() or after key has been deleted with
|
||||
// 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));
|
||||
__get_tls()->tib_keys[k] = val;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -16,13 +16,19 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
#ifdef pthread_spin_destroy
|
||||
#undef pthread_spin_destroy
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Destroys spin lock.
|
||||
*
|
||||
* @return 0 on success, or errno on error
|
||||
*/
|
||||
errno_t(pthread_spin_destroy)(pthread_spinlock_t *spin) {
|
||||
return pthread_spin_destroy(spin);
|
||||
errno_t pthread_spin_destroy(pthread_spinlock_t *spin) {
|
||||
atomic_store_explicit(&spin->_lock, -1, memory_order_relaxed);
|
||||
return 0;
|
||||
}
|
|
@ -16,8 +16,13 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
#ifdef pthread_spin_init
|
||||
#undef pthread_spin_init
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initializes spin lock.
|
||||
*
|
||||
|
@ -27,6 +32,7 @@
|
|||
* @see pthread_spin_destroy
|
||||
* @see pthread_spin_lock
|
||||
*/
|
||||
errno_t(pthread_spin_init)(pthread_spinlock_t *spin, int pshared) {
|
||||
return pthread_spin_init(spin, pshared);
|
||||
errno_t pthread_spin_init(pthread_spinlock_t *spin, int pshared) {
|
||||
atomic_store_explicit(&spin->_lock, 0, memory_order_relaxed);
|
||||
return 0;
|
||||
}
|
|
@ -16,20 +16,16 @@
|
|||
│ 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/thread/thread.h"
|
||||
|
||||
#ifdef pthread_spin_lock
|
||||
#undef pthread_spin_lock
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Acquires spin lock.
|
||||
*
|
||||
* 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:
|
||||
* Acquires spin lock, e.g.
|
||||
*
|
||||
* pthread_spinlock_t lock;
|
||||
* pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
|
||||
|
@ -38,18 +34,20 @@
|
|||
* pthread_spin_unlock(&lock);
|
||||
* pthread_spin_destroy(&lock);
|
||||
*
|
||||
* Cosmopolitan permits succinct notation for spin locks:
|
||||
*
|
||||
* pthread_spinlock_t lock = {0};
|
||||
* pthread_spin_lock(&lock);
|
||||
* // do work...
|
||||
* pthread_spin_unlock(&lock);
|
||||
* This function has undefined behavior when `spin` wasn't intialized,
|
||||
* was destroyed, or if the lock's already held by the calling thread.
|
||||
*
|
||||
* @return 0 on success, or errno on error
|
||||
* @see pthread_spin_trylock
|
||||
* @see pthread_spin_unlock
|
||||
* @see pthread_spin_init
|
||||
*/
|
||||
errno_t(pthread_spin_lock)(pthread_spinlock_t *spin) {
|
||||
return pthread_spin_lock(spin);
|
||||
errno_t pthread_spin_lock(pthread_spinlock_t *spin) {
|
||||
int x;
|
||||
for (;;) {
|
||||
x = atomic_exchange_explicit(&spin->_lock, 1, memory_order_acquire);
|
||||
if (!x) break;
|
||||
_unassert(x == 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -16,17 +16,28 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
#ifdef pthread_spin_trylock
|
||||
#undef pthread_spin_trylock
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Acquires spin lock if available.
|
||||
*
|
||||
* Unlike pthread_spin_lock() this function won't block, and instead
|
||||
* returns an error immediately if the spinlock couldn't be acquired
|
||||
* This function has undefined behavior when `spin` wasn't intialized,
|
||||
* was destroyed, or if the lock's already held by the calling thread.
|
||||
*
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise EBUSY if lock is already held
|
||||
*/
|
||||
errno_t(pthread_spin_trylock)(pthread_spinlock_t *spin) {
|
||||
return pthread_spin_trylock(spin);
|
||||
errno_t pthread_spin_trylock(pthread_spinlock_t *spin) {
|
||||
int x;
|
||||
x = atomic_exchange_explicit(&spin->_lock, 1, memory_order_acquire);
|
||||
if (!x) return 0;
|
||||
_unassert(x == 1);
|
||||
return EBUSY;
|
||||
}
|
|
@ -16,14 +16,23 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
#ifdef pthread_spin_unlock
|
||||
#undef pthread_spin_unlock
|
||||
#endif
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @see pthread_spin_lock
|
||||
*/
|
||||
errno_t(pthread_spin_unlock)(pthread_spinlock_t *spin) {
|
||||
return pthread_spin_unlock(spin);
|
||||
errno_t pthread_spin_unlock(pthread_spinlock_t *spin) {
|
||||
atomic_store_explicit(&spin->_lock, 0, memory_order_release);
|
||||
return 0;
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
│ 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/calls/struct/rusage.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)) {
|
||||
ShowHint("won't print addr2line backtrace because pledge");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(debugbin = FindDebugBinary())) {
|
||||
ShowHint("won't print addr2line backtrace because no debug binary");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -171,11 +174,14 @@ static int PrintBacktrace(int fd, const struct StackFrame *bp) {
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
ShowHint("won't print addr2line backtrace because no dwarf");
|
||||
#endif
|
||||
return PrintBacktraceUsingSymbols(fd, bp, GetSymbolTable());
|
||||
}
|
||||
|
||||
void ShowBacktrace(int fd, const struct StackFrame *bp) {
|
||||
BLOCK_CANCELLATIONS;
|
||||
#ifdef __FNO_OMIT_FRAME_POINTER__
|
||||
/* asan runtime depends on this function */
|
||||
ftrace_enabled(-1);
|
||||
|
@ -189,4 +195,5 @@ void ShowBacktrace(int fd, const struct StackFrame *bp) {
|
|||
"\t-D__FNO_OMIT_FRAME_POINTER__\n"
|
||||
"\t-fno-omit-frame-pointer\n");
|
||||
#endif
|
||||
ALLOW_CANCELLATIONS;
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ WinThreadEntry(int rdi, // rcx
|
|||
*wt->ztid = 0;
|
||||
__imp_WakeByAddressAll(wt->ztid);
|
||||
// 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);
|
||||
notpossible;
|
||||
}
|
||||
|
|
|
@ -28,15 +28,19 @@
|
|||
#include "libc/runtime/internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
#define _TLSZ ((intptr_t)_tls_size)
|
||||
#define _TLDZ ((intptr_t)_tdata_size)
|
||||
#define _TIBZ sizeof(struct CosmoTib)
|
||||
|
||||
struct PosixThread _pthread_main;
|
||||
extern unsigned char __tls_mov_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];
|
||||
|
||||
/**
|
||||
|
@ -79,6 +83,7 @@ void __enable_tls(void) {
|
|||
size_t siz;
|
||||
struct CosmoTib *tib;
|
||||
char *mem, *tls;
|
||||
|
||||
siz = ROUNDUP(_TLSZ + _TIBZ, _Alignof(__static_tls));
|
||||
if (siz <= sizeof(__static_tls)) {
|
||||
// if tls requirement is small then use the static tls block
|
||||
|
@ -95,12 +100,15 @@ void __enable_tls(void) {
|
|||
mem = _weaken(_mapanon)(siz);
|
||||
_npassert(mem);
|
||||
}
|
||||
|
||||
if (IsAsan()) {
|
||||
// poison the space between .tdata and .tbss
|
||||
__asan_poison(mem + (intptr_t)_tdata_size,
|
||||
(intptr_t)_tbss_offset - (intptr_t)_tdata_size,
|
||||
kAsanProtected);
|
||||
}
|
||||
|
||||
// initialize main thread tls memory
|
||||
tib = (struct CosmoTib *)(mem + siz - _TIBZ);
|
||||
tls = mem + siz - _TIBZ - _TLSZ;
|
||||
tib->tib_self = tib;
|
||||
|
@ -117,9 +125,16 @@ void __enable_tls(void) {
|
|||
tid = sys_gettid();
|
||||
}
|
||||
atomic_store_explicit(&tib->tib_tid, tid, memory_order_relaxed);
|
||||
_pthread_main.ptid = tid;
|
||||
|
||||
// initialize posix threads
|
||||
_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);
|
||||
|
||||
// ask the operating system to change the x86 segment register
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "libc/calls/wincrash.internal.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/directmap.internal.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
|
@ -237,7 +238,7 @@ textwindows void WinMainForked(void) {
|
|||
for (i = 0; i < fds->n; ++i) {
|
||||
if (fds->p[i].kind == kFdProcess) {
|
||||
fds->p[i].kind = 0;
|
||||
fds->f = MIN(i, fds->f);
|
||||
atomic_store_explicit(&fds->f, MIN(i, fds->f), memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -218,6 +218,8 @@ void testlib_runtestcases(testfn_t *start, testfn_t *end, testfn_t warmup) {
|
|||
}
|
||||
if (_weaken(SetUpOnce)) _weaken(SetUpOnce)();
|
||||
for (x = 0, fn = start; fn != end; ++fn) {
|
||||
STRACE("");
|
||||
STRACE("# setting up %t", fn);
|
||||
if (_weaken(testlib_enable_tmp_setup_teardown)) SetupTmpDir();
|
||||
if (_weaken(SetUp)) _weaken(SetUp)();
|
||||
errno = 0;
|
||||
|
@ -229,6 +231,7 @@ void testlib_runtestcases(testfn_t *start, testfn_t *end, testfn_t warmup) {
|
|||
STRACE("# running test %t", fn);
|
||||
(*fn)();
|
||||
STRACE("");
|
||||
STRACE("# tearing down %t", fn);
|
||||
if (!IsWindows()) sys_getpid();
|
||||
if (_weaken(TearDown)) _weaken(TearDown)();
|
||||
if (_weaken(testlib_enable_tmp_setup_teardown)) TearDownTmpDir();
|
||||
|
|
|
@ -5,14 +5,16 @@
|
|||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "third_party/nsync/dll.h"
|
||||
|
||||
#define PT_OWNSTACK 1
|
||||
#define PT_MAINTHREAD 2
|
||||
#define PT_STATIC 2
|
||||
#define PT_ASYNC 4
|
||||
#define PT_NOCANCEL 8
|
||||
#define PT_MASKED 16
|
||||
#define PT_INCANCEL 32
|
||||
#define PT_OPENBSD_KLUDGE 64
|
||||
#define PT_EXITING 128
|
||||
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
@ -66,14 +68,15 @@ struct PosixThread {
|
|||
int flags; // 0x00: see PT_* constants
|
||||
_Atomic(int) cancelled; // 0x04: thread has bad beliefs
|
||||
_Atomic(enum PosixThreadStatus) status;
|
||||
_Atomic(int) ptid; // transitions 0 → tid
|
||||
void *(*start)(void *); // creation callback
|
||||
void *arg; // start's parameter
|
||||
void *rc; // start's return value
|
||||
char *altstack; // thread sigaltstack
|
||||
char *tls; // bottom of tls allocation
|
||||
struct CosmoTib *tib; // middle of tls allocation
|
||||
jmp_buf exiter; // for pthread_exit
|
||||
_Atomic(int) ptid; // transitions 0 → tid
|
||||
void *(*start)(void *); // creation callback
|
||||
void *arg; // start's parameter
|
||||
void *rc; // start's return value
|
||||
char *altstack; // thread sigaltstack
|
||||
char *tls; // bottom of tls allocation
|
||||
struct CosmoTib *tib; // middle of tls allocation
|
||||
nsync_dll_element_ list; // list of threads
|
||||
jmp_buf exiter; // for pthread_exit
|
||||
pthread_attr_t attr;
|
||||
sigset_t sigmask;
|
||||
struct _pthread_cleanup_buffer *cleanup;
|
||||
|
@ -81,24 +84,19 @@ struct PosixThread {
|
|||
|
||||
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;
|
||||
|
||||
int _pthread_atfork(atfork_f, atfork_f, atfork_f) _Hide;
|
||||
int _pthread_reschedule(struct PosixThread *) _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_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_parent(void) _Hide;
|
||||
void _pthread_onfork_child(void) _Hide;
|
||||
void _pthread_ungarbage(void) _Hide;
|
||||
int _pthread_cancel_sys(void) _Hide;
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/kmalloc.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/runtime/memtrack.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
|
@ -34,6 +35,14 @@ static struct AtForks {
|
|||
} * list;
|
||||
} _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) {
|
||||
struct AtFork *a;
|
||||
struct PosixThread *pt;
|
||||
|
@ -44,17 +53,11 @@ static void _pthread_onfork(int i) {
|
|||
_atforks.list = a;
|
||||
}
|
||||
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) {
|
||||
_pthread_onfork(0);
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
__kmalloc_lock();
|
||||
__mmi_lock();
|
||||
}
|
||||
|
@ -62,6 +65,7 @@ void _pthread_onfork_prepare(void) {
|
|||
void _pthread_onfork_parent(void) {
|
||||
__mmi_unlock();
|
||||
__kmalloc_unlock();
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
_pthread_onfork(1);
|
||||
}
|
||||
|
||||
|
@ -72,12 +76,26 @@ void _pthread_onfork_child(void) {
|
|||
extern pthread_mutex_t __mmi_lock_obj;
|
||||
tib = __get_tls();
|
||||
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);
|
||||
|
||||
// wipe core runtime locks
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
pthread_mutex_init(&__mmi_lock_obj, &attr);
|
||||
pthread_spin_init(&_pthread_lock, 0);
|
||||
__kmalloc_unlock();
|
||||
|
||||
// call user-supplied forked child callbacks
|
||||
_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) {
|
||||
|
|
|
@ -307,7 +307,7 @@ errno_t pthread_cancel(pthread_t thread) {
|
|||
void pthread_testcancel(void) {
|
||||
struct PosixThread *pt;
|
||||
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_MASKED) || (pt->flags & PT_ASYNC)) &&
|
||||
atomic_load_explicit(&pt->cancelled, memory_order_acquire)) {
|
||||
|
@ -335,7 +335,7 @@ errno_t pthread_testcancel_np(void) {
|
|||
int rc;
|
||||
struct PosixThread *pt;
|
||||
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 (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return 0;
|
||||
if (!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) {
|
||||
|
|
|
@ -39,7 +39,9 @@
|
|||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/spawn.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "libc/thread/wait0.internal.h"
|
||||
#include "third_party/nsync/dll.h"
|
||||
|
||||
STATIC_YOINK("nsync_mu_lock");
|
||||
STATIC_YOINK("nsync_mu_unlock");
|
||||
|
@ -48,19 +50,13 @@ STATIC_YOINK("_pthread_atfork");
|
|||
#define MAP_ANON_OPENBSD 0x1000
|
||||
#define MAP_STACK_OPENBSD 0x4000
|
||||
|
||||
errno_t _pthread_wait(struct PosixThread *pt) {
|
||||
return _wait0(&pt->tib->tib_tid);
|
||||
}
|
||||
|
||||
void _pthread_free(struct PosixThread *pt) {
|
||||
if (pt->flags & PT_MAINTHREAD) return;
|
||||
if (pt->flags & PT_STATIC) return;
|
||||
free(pt->tls);
|
||||
if ((pt->flags & PT_OWNSTACK) && //
|
||||
pt->attr.__stackaddr && //
|
||||
pt->attr.__stackaddr != MAP_FAILED) {
|
||||
if (munmap(pt->attr.__stackaddr, pt->attr.__stacksize)) {
|
||||
notpossible;
|
||||
}
|
||||
_npassert(!munmap(pt->attr.__stackaddr, pt->attr.__stacksize));
|
||||
}
|
||||
if (pt->altstack) {
|
||||
free(pt->altstack);
|
||||
|
@ -69,9 +65,11 @@ void _pthread_free(struct PosixThread *pt) {
|
|||
}
|
||||
|
||||
static int PosixThread(void *arg, int tid) {
|
||||
void *rc;
|
||||
struct sigaltstack ss;
|
||||
struct PosixThread *pt = arg;
|
||||
enum PosixThreadStatus status;
|
||||
struct sigaltstack ss;
|
||||
_unassert(__get_tls()->tib_tid > 0);
|
||||
if (pt->altstack) {
|
||||
ss.ss_flags = 0;
|
||||
ss.ss_size = SIGSTKSZ;
|
||||
|
@ -87,12 +85,12 @@ static int PosixThread(void *arg, int tid) {
|
|||
if (!setjmp(pt->exiter)) {
|
||||
__get_tls()->tib_pthread = (pthread_t)pt;
|
||||
_sigsetmask(pt->sigmask);
|
||||
pt->rc = pt->start(pt->arg);
|
||||
rc = pt->start(pt->arg);
|
||||
// ensure pthread_cleanup_pop(), and pthread_exit() popped 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 0;
|
||||
}
|
||||
|
@ -234,13 +232,19 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
case PTHREAD_CREATE_DETACHED:
|
||||
atomic_store_explicit(&pt->status, kPosixThreadDetached,
|
||||
memory_order_relaxed);
|
||||
_pthread_zombies_add(pt);
|
||||
break;
|
||||
default:
|
||||
_pthread_free(pt);
|
||||
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
|
||||
pt->sigmask = oldsigs;
|
||||
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_CHILD_SETTID | CLONE_CHILD_CLEARTID,
|
||||
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);
|
||||
return rc;
|
||||
}
|
||||
|
@ -313,7 +320,7 @@ errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
|||
void *(*start_routine)(void *), void *arg) {
|
||||
errno_t rc;
|
||||
__require_tls();
|
||||
_pthread_zombies_decimate();
|
||||
pthread_decimate_np();
|
||||
BLOCK_SIGNALS;
|
||||
rc = pthread_create_impl(thread, attr, start_routine, arg, _SigMask);
|
||||
ALLOW_SIGNALS;
|
||||
|
|
|
@ -17,68 +17,33 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/spawn.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "third_party/nsync/dll.h"
|
||||
|
||||
// TODO(jart): track all threads, not just zombies
|
||||
|
||||
static struct Zombie {
|
||||
struct Zombie *next;
|
||||
/**
|
||||
* Releases memory of detached threads that have terminated.
|
||||
*/
|
||||
void pthread_decimate_np(void) {
|
||||
nsync_dll_element_ *e;
|
||||
struct PosixThread *pt;
|
||||
} * _pthread_zombies;
|
||||
|
||||
void _pthread_zombies_add(struct PosixThread *pt) {
|
||||
struct Zombie *z;
|
||||
if ((z = malloc(sizeof(struct Zombie)))) {
|
||||
z->pt = pt;
|
||||
z->next = atomic_load(&_pthread_zombies);
|
||||
for (;;) {
|
||||
if (atomic_compare_exchange_weak(&_pthread_zombies, &z->next, z)) {
|
||||
break;
|
||||
}
|
||||
enum PosixThreadStatus status;
|
||||
StartOver:
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
for (e = nsync_dll_first_(_pthread_list); e;
|
||||
e = nsync_dll_next_(_pthread_list, e)) {
|
||||
pt = (struct PosixThread *)e->container;
|
||||
if (pt->tib == __get_tls()) continue;
|
||||
status = atomic_load_explicit(&pt->status, memory_order_acquire);
|
||||
if (status != kPosixThreadZombie) 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
}
|
|
@ -16,45 +16,52 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/spawn.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
|
||||
* @raise EINVAL if thread is null or already detached
|
||||
* @returnserrno
|
||||
* @threadsafe
|
||||
*/
|
||||
errno_t pthread_detach(pthread_t thread) {
|
||||
struct PosixThread *pt;
|
||||
enum PosixThreadStatus status, transition;
|
||||
if (!(pt = (struct PosixThread *)thread)) return EINVAL;
|
||||
for (;;) {
|
||||
for (pt = (struct PosixThread *)thread;;) {
|
||||
status = atomic_load_explicit(&pt->status, memory_order_acquire);
|
||||
if (status == kPosixThreadDetached || status == kPosixThreadZombie) {
|
||||
// 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) {
|
||||
if (status == kPosixThreadJoinable) {
|
||||
transition = kPosixThreadDetached;
|
||||
} else if (status == kPosixThreadTerminated) {
|
||||
transition = kPosixThreadZombie;
|
||||
} else {
|
||||
notpossible;
|
||||
unreachable;
|
||||
}
|
||||
if (atomic_compare_exchange_weak_explicit(&pt->status, &status, transition,
|
||||
memory_order_release,
|
||||
memory_order_relaxed)) {
|
||||
_pthread_zombies_add(pt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (transition == kPosixThreadZombie) {
|
||||
_pthread_zombify(pt);
|
||||
}
|
||||
pthread_decimate_np();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -16,16 +16,52 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/atomic.h"
|
||||
#include "libc/dce.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/mem/gc.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "third_party/nsync/dll.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.
|
||||
*
|
||||
|
@ -60,35 +96,59 @@
|
|||
* @noreturn
|
||||
*/
|
||||
wontreturn void pthread_exit(void *rc) {
|
||||
struct CosmoTib *tib;
|
||||
struct PosixThread *pt;
|
||||
struct _pthread_cleanup_buffer *cb;
|
||||
pt = (struct PosixThread *)__get_tls()->tib_pthread;
|
||||
enum PosixThreadStatus status, transition;
|
||||
|
||||
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;
|
||||
// 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);
|
||||
|
||||
// free resources
|
||||
CleanupThread(pt);
|
||||
DestroyTlsKeys(tib);
|
||||
_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.
|
||||
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 {
|
||||
// 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.
|
||||
atomic_store_explicit(&__get_tls()->tib_tid, 0, memory_order_release);
|
||||
nsync_futex_wake_(&__get_tls()->tib_tid, INT_MAX, !IsWindows());
|
||||
|
||||
// check if this is the main thread or an orphaned thread
|
||||
if (pthread_orphan_np()) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// check if the main thread has died whilst children live
|
||||
// note that the main thread is joinable by child threads
|
||||
if (pt->flags & PT_STATIC) {
|
||||
atomic_store_explicit(&tib->tib_tid, 0, memory_order_release);
|
||||
nsync_futex_wake_(&tib->tib_tid, INT_MAX, !IsWindows());
|
||||
_Exit1(0);
|
||||
}
|
||||
|
||||
// this is a child thread
|
||||
longjmp(pt->exiter, 1);
|
||||
}
|
||||
|
|
|
@ -16,21 +16,29 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "libc/thread/wait0.internal.h"
|
||||
|
||||
/**
|
||||
* 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
|
||||
* if the thread called pthread_exit(), or `PTHREAD_CANCELED` if
|
||||
* pthread_cancel() destroyed the thread instead
|
||||
* @return 0 on success, or errno on error
|
||||
* @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
|
||||
* @returnserrno
|
||||
* @threadsafe
|
||||
|
@ -38,20 +46,22 @@
|
|||
errno_t pthread_join(pthread_t thread, void **value_ptr) {
|
||||
errno_t rc;
|
||||
struct PosixThread *pt;
|
||||
if (thread == __get_tls()->tib_pthread) {
|
||||
return EDEADLK;
|
||||
enum PosixThreadStatus status;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -16,23 +16,17 @@
|
|||
│ 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)();
|
||||
}
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* Returns true if calling thread is the only thread.
|
||||
*/
|
||||
bool pthread_orphan_np(void) {
|
||||
bool res;
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
res = _pthread_list == _pthread_list->prev &&
|
||||
_pthread_list == _pthread_list->next;
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
return res;
|
||||
}
|
91
libc/thread/pthread_print_np.c
Normal file
91
libc/thread/pthread_print_np.c
Normal 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;
|
||||
}
|
|
@ -23,9 +23,12 @@
|
|||
void _pthread_ungarbage(void) {
|
||||
int i;
|
||||
struct Garbages *g;
|
||||
if ((g = __get_tls()->tib_garbages)) {
|
||||
for (i = g->i; i--;) {
|
||||
((void (*)(intptr_t))g->p[i].fn)(g->p[i].arg);
|
||||
struct CosmoTib *tib;
|
||||
tib = __get_tls();
|
||||
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);
|
||||
|
|
|
@ -16,30 +16,13 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "third_party/nsync/dll.h"
|
||||
|
||||
void _pthread_key_destruct(void) {
|
||||
int i, j, gotsome;
|
||||
void *val, **keys;
|
||||
pthread_key_dtor dtor;
|
||||
if (!__tls_enabled) return;
|
||||
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;
|
||||
}
|
||||
}
|
||||
void _pthread_zombify(struct PosixThread *pt) {
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
_pthread_list = nsync_dll_remove_(_pthread_list, &pt->list);
|
||||
_pthread_list = nsync_dll_make_first_in_list_(_pthread_list, &pt->list);
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
}
|
|
@ -110,10 +110,13 @@ int pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *),
|
|||
void *);
|
||||
|
||||
int pthread_yield(void);
|
||||
bool pthread_orphan_np(void);
|
||||
void pthread_testcancel(void);
|
||||
void pthread_decimate_np(void);
|
||||
int pthread_testcancel_np(void);
|
||||
void pthread_exit(void *) wontreturn;
|
||||
pthread_t pthread_self(void) pureconst;
|
||||
int pthread_print_np(int, const char *, ...);
|
||||
pthread_id_np_t pthread_getthreadid_np(void);
|
||||
int pthread_getunique_np(pthread_t, pthread_id_np_t *);
|
||||
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 && \
|
||||
!defined(__STRICT_ANSI__)
|
||||
!defined(__STRICT_ANSI__) && !defined(MODE_DBG)
|
||||
extern const errno_t EBUSY;
|
||||
#define pthread_spin_lock(pSpin) \
|
||||
({ \
|
||||
|
|
|
@ -45,18 +45,17 @@ extern unsigned __tls_index;
|
|||
void __require_tls(void);
|
||||
void __set_tls(struct CosmoTib *);
|
||||
|
||||
#if defined(__GNUC__) && defined(__x86_64__) && !defined(__STRICT_ANSI__)
|
||||
/**
|
||||
* Returns location of thread information block.
|
||||
*
|
||||
* This can't be used in privileged functions.
|
||||
*/
|
||||
static inline struct CosmoTib *__get_tls(void) {
|
||||
struct CosmoTib *_tib;
|
||||
asm("mov\t%%fs:0,%0" : "=r"(_tib) : /* no inputs */ : "memory");
|
||||
return _tib;
|
||||
}
|
||||
#endif /* GNU x86-64 */
|
||||
#define __get_tls() \
|
||||
({ \
|
||||
struct CosmoTib *_t; \
|
||||
asm("mov\t%%fs:0,%0" : "=r"(_t) : /* no inputs */ : "memory"); \
|
||||
_t; \
|
||||
})
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/cp.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "libc/thread/wait0.internal.h"
|
||||
#include "third_party/nsync/futex.internal.h"
|
||||
|
||||
|
@ -38,13 +41,21 @@
|
|||
*/
|
||||
errno_t _wait0(const atomic_int *ctid) {
|
||||
int x, rc = 0;
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
|
||||
if (nsync_futex_wait_(ctid, x, !IsWindows(), 0) == -ECANCELED) {
|
||||
rc = ECANCELED;
|
||||
break;
|
||||
// "The behavior is undefined if the value specified by the thread
|
||||
// argument to pthread_join() refers to the calling thread."
|
||||
// ──Quoth POSIX.1-2017
|
||||
_unassert(ctid != &__get_tls()->tib_tid);
|
||||
// "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;
|
||||
}
|
||||
|
|
|
@ -116,7 +116,9 @@ static int __zipos_mkfd(int minfd) {
|
|||
|
||||
static int __zipos_setfd(int fd, struct ZiposHandle *h, unsigned flags,
|
||||
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].handle = (intptr_t)h;
|
||||
g_fds.p[fd].flags = flags | O_CLOEXEC;
|
||||
|
|
|
@ -54,16 +54,11 @@ void TriggerSignal(void) {
|
|||
}
|
||||
|
||||
static void *Increment(void *arg) {
|
||||
ASSERT_EQ(EDEADLK, pthread_join(pthread_self(), 0));
|
||||
ASSERT_EQ(gettid(), pthread_getthreadid_np());
|
||||
TriggerSignal();
|
||||
return (void *)((uintptr_t)arg + 1);
|
||||
}
|
||||
|
||||
TEST(pthread_create, joinSelfDeadlocks) {
|
||||
ASSERT_EQ(EDEADLK, pthread_join(pthread_self(), 0));
|
||||
}
|
||||
|
||||
TEST(pthread_create, testCreateReturnJoin) {
|
||||
void *rc;
|
||||
pthread_t id;
|
||||
|
@ -279,4 +274,7 @@ BENCH(pthread_create, bench) {
|
|||
EZBENCH2("CreateJoin", donothing, CreateJoin());
|
||||
EZBENCH2("CreateDetach", donothing, CreateDetach());
|
||||
EZBENCH2("CreateDetached", donothing, CreateDetached());
|
||||
while (!pthread_orphan_np()) {
|
||||
pthread_decimate_np();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,13 @@
|
|||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/sigaction.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/sysv/consts/sa.h"
|
||||
#include "libc/sysv/consts/sig.h"
|
||||
#include "libc/testlib/testlib.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "third_party/nsync/dll.h"
|
||||
|
||||
void OnUsr1(int sig, struct siginfo *si, void *vctx) {
|
||||
struct ucontext *ctx = vctx;
|
||||
|
@ -40,7 +43,6 @@ void TriggerSignal(void) {
|
|||
}
|
||||
|
||||
static void *Increment(void *arg) {
|
||||
ASSERT_EQ(EDEADLK, pthread_join(pthread_self(), 0));
|
||||
ASSERT_EQ(gettid(), pthread_getthreadid_np());
|
||||
TriggerSignal();
|
||||
return (void *)((uintptr_t)arg + 1);
|
||||
|
@ -50,6 +52,9 @@ TEST(pthread_detach, testCreateReturn) {
|
|||
pthread_t id;
|
||||
ASSERT_EQ(0, pthread_create(&id, 0, Increment, 0));
|
||||
ASSERT_EQ(0, pthread_detach(id));
|
||||
while (!pthread_orphan_np()) {
|
||||
pthread_decimate_np();
|
||||
}
|
||||
}
|
||||
|
||||
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_create(0, &attr, Increment, 0));
|
||||
ASSERT_EQ(0, pthread_attr_destroy(&attr));
|
||||
while (!pthread_orphan_np()) {
|
||||
pthread_decimate_np();
|
||||
}
|
||||
}
|
||||
|
|
66
test/libc/thread/pthread_exit_test.c
Normal file
66
test/libc/thread/pthread_exit_test.c
Normal 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);
|
||||
}
|
|
@ -16,23 +16,56 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/atomic.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/nexgen32e/nexgen32e.h"
|
||||
#include "libc/testlib/testlib.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
TEST(pthread_key_create, testRunsDtors_becauseNoLeakReport) {
|
||||
void *x;
|
||||
pthread_key_t key;
|
||||
x = malloc(123);
|
||||
EXPECT_EQ(0, pthread_key_create(&key, free));
|
||||
EXPECT_EQ(0, pthread_setspecific(key, x));
|
||||
EXPECT_EQ(x, pthread_getspecific(key));
|
||||
x = malloc(123);
|
||||
EXPECT_EQ(0, pthread_key_create(&key, free));
|
||||
EXPECT_EQ(0, pthread_setspecific(key, x));
|
||||
EXPECT_EQ(x, pthread_getspecific(key));
|
||||
x = malloc(123);
|
||||
EXPECT_EQ(0, pthread_key_create(&key, free));
|
||||
EXPECT_EQ(0, pthread_setspecific(key, x));
|
||||
EXPECT_EQ(x, pthread_getspecific(key));
|
||||
pthread_key_t mykey;
|
||||
int destructor_calls;
|
||||
|
||||
void KeyDestructor(void *arg) {
|
||||
EXPECT_EQ(0, pthread_getspecific(mykey));
|
||||
ASSERT_EQ(31337, (intptr_t)arg);
|
||||
++destructor_calls;
|
||||
}
|
||||
|
||||
void *Worker(void *arg) {
|
||||
ASSERT_EQ(0, pthread_getspecific(mykey));
|
||||
ASSERT_EQ(0, pthread_setspecific(mykey, (void *)31337));
|
||||
ASSERT_EQ((void *)31337, pthread_getspecific(mykey));
|
||||
return 0;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue