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

@ -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_

View file

@ -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) {

View file

@ -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)) {

View file

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

View file

@ -16,23 +16,34 @@
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/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"
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);
/**
* Releases memory of detached threads that have terminated.
*/
void pthread_decimate_np(void) {
nsync_dll_element_ *e;
struct PosixThread *pt;
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;
}
}
pthread_spin_unlock(&_pthread_lock);
}

View file

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

View file

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

View file

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

View file

@ -16,14 +16,17 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
/**
* Releases spin lock.
*
* @return 0 on success, or errno on error
* @see pthread_spin_lock
* Returns true if calling thread is the only thread.
*/
errno_t(pthread_spin_unlock)(pthread_spinlock_t *spin) {
return pthread_spin_unlock(spin);
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;
}

View file

@ -16,69 +16,76 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/atomic.h"
#include "libc/mem/mem.h"
#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/spawn.h"
#include "libc/thread/thread.h"
#include "third_party/nsync/dll.h"
// TODO(jart): track all threads, not just zombies
#define N 2048
#define M 15
static struct Zombie {
struct Zombie *next;
#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;
} * _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;
}
}
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");
}
}
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);
}
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);
}
}
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);
BLOCK_CANCELLATIONS;
strace_enabled(-1);
rc = write(fd, buf, strlen(buf));
strace_enabled(+1);
ALLOW_CANCELLATIONS;
return rc;
}

View file

@ -1,32 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/thread.h"
/**
* Initializes spin lock.
*
* @param pshared is ignored, since this implementation always permits
* multiple processes to operate on the same spin locks
* @return 0 on success, or errno on error
* @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);
}

View file

@ -1,55 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/thread.h"
/**
* 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:
*
* pthread_spinlock_t lock;
* pthread_spin_init(&lock, PTHREAD_PROCESS_PRIVATE);
* pthread_spin_lock(&lock);
* // do work...
* 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);
*
* @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);
}

View file

@ -1,32 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/thread.h"
/**
* 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
*
* @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);
}

View file

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

View file

@ -16,13 +16,13 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "third_party/nsync/dll.h"
/**
* 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);
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);
}

View file

@ -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) \
({ \

View file

@ -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) */

View file

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