Make some fixups to POSIX threads

This commit is contained in:
Justine Tunney 2022-09-07 21:13:50 -07:00
parent de511bc71a
commit 6c323383e5
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
12 changed files with 168 additions and 69 deletions

View file

@ -593,7 +593,7 @@ typedef struct {
#define notpossible \
do { \
asm("hlt"); \
asm("ud2"); \
unreachable; \
} while (0)

View file

@ -18,38 +18,56 @@
*/
#include "libc/calls/strace.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asmflag.h"
#include "libc/intrin/promises.internal.h"
#include "libc/nt/thread.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/nr.h"
__msabi extern typeof(ExitThread) *const __imp_ExitThread;
/**
* Terminates thread with raw system call.
*
* If this is the main thread, or an orphaned child thread, then this
* function is equivalent to exiting the process; however, `rc` shall
* only be reported to the parent process on Linux, FreeBSD & Windows
* whereas on other platforms, it'll be silently coerced to zero.
*
* @param rc only works on Linux and Windows
* @see cthread_exit()
* @threadsafe
* @noreturn
*/
privileged wontreturn void _Exit1(int rc) {
struct WinThread *wt;
char cf;
int ax, dx, di, si;
STRACE("_Exit1(%d)", rc);
if (!IsWindows() && !IsMetal()) {
if (IsOpenbsd() && !PLEDGED(STDIO)) {
// exit() on Linux
// thr_exit() on FreeBSD
// __threxit() on OpenBSD
// __lwp_exit() on NetBSD
// __bsdthread_terminate() on XNU
asm volatile(CFLAG_ASM("xor\t%%r10d,%%r10d\n\t"
"syscall")
: CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx), "=D"(di), "=S"(si)
: "1"(__NR_exit), "3"(IsLinux() ? rc : 0), "4"(0), "2"(0)
: "rcx", "r8", "r9", "r10", "r11", "memory");
if ((IsFreebsd() && !cf && !ax) || (SupportsFreebsd() && IsTiny())) {
// FreeBSD checks if this is either the main thread by itself, or
// the last running child thread in which case thr_exit() returns
// zero with an error. In that case we'll exit the whole process.
// FreeBSD thr_exit() can even clobber registers, like r8 and r9!
asm volatile("syscall"
: /* no outputs */
: "a"(__NR_exit), "D"(rc)
: "a"(__NR_exit_group), "D"(rc)
: "rcx", "r11", "memory");
unreachable;
}
asm volatile("xor\t%%r10d,%%r10d\n\t"
"syscall"
: /* no outputs */
: "a"(__NR_exit), "D"(IsLinux() ? rc : 0), "S"(0), "d"(0)
: "rcx", "r10", "r11", "memory");
} else if (IsWindows()) {
ExitThread(rc);
}
for (;;) {
asm("ud2");
__imp_ExitThread(rc);
unreachable;
}
notpossible;
}

View file

@ -100,7 +100,7 @@ static int pthread_mutex_lock_spin(pthread_mutex_t *mutex, int expect,
* PTHREAD_MUTEX_ERRORCHECK : 917c (296ns)
*
* @return 0 on success, or error number on failure
* @see pthread_spin_lock
* @see pthread_spin_lock()
*/
int pthread_mutex_lock(pthread_mutex_t *mutex) {
int c, me, owner, tries;

View file

@ -10,10 +10,46 @@ COSMOPOLITAN_C_START_
* @fileoverview Cosmopolitan POSIX Thread Internals
*/
// LEGAL TRANSITIONS ┌──> TERMINATED
// pthread_create ─┬─> JOINABLE ─┴┬─> DETACHED ──> ZOMBIE
// └──────────────┘
enum PosixThreadStatus {
// this is a running thread that needs pthread_join()
//
// the following transitions are possible:
//
// - kPosixThreadJoinable -> kPosixThreadTerminated if start_routine()
// returns, or is longjmp'd out of by pthread_exit(), and the thread
// is waiting to be joined.
//
// - kPosixThreadJoinable -> kPosixThreadDetached if pthread_detach()
// is called on this thread.
kPosixThreadJoinable,
// this is a managed thread that'll be cleaned up by the library.
//
// the following transitions are possible:
//
// - kPosixThreadDetached -> kPosixThreadZombie if start_routine()
// returns, or is longjmp'd out of by pthread_exit(), and the thread
// is waiting to be joined.
kPosixThreadDetached,
// this is a joinable thread that terminated.
//
// the following transitions are possible:
//
// - kPosixThreadTerminated -> _pthread_free() will happen when
// pthread_join() is called by the user.
kPosixThreadTerminated,
// this is a detached thread that terminated.
//
// the following transitions are possible:
//
// - kPosixThreadZombie -> _pthread_free() will happen whenever
// convenient, e.g. pthread_create() entry or atexit handler.
kPosixThreadZombie,
};
@ -28,11 +64,11 @@ struct PosixThread {
pthread_attr_t attr;
};
void pthread_free(struct PosixThread *) hidden;
void pthread_wait(struct PosixThread *) hidden;
void pthread_zombies_add(struct PosixThread *) hidden;
void pthread_zombies_decimate(void) hidden;
void pthread_zombies_harvest(void) hidden;
void _pthread_free(struct PosixThread *) hidden;
void _pthread_wait(struct PosixThread *) hidden;
void _pthread_zombies_add(struct PosixThread *) hidden;
void _pthread_zombies_decimate(void) hidden;
void _pthread_zombies_harvest(void) hidden;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -22,21 +22,23 @@
#include "libc/intrin/atomic.h"
#include "libc/intrin/pthread.h"
#include "libc/intrin/wait0.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/gettls.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/clone.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#include "libc/thread/internal.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
void pthread_wait(struct PosixThread *pt) {
void _pthread_wait(struct PosixThread *pt) {
_wait0(pt->spawn.ctid);
}
void pthread_free(struct PosixThread *pt) {
void _pthread_free(struct PosixThread *pt) {
free(pt->spawn.tls);
if (pt->stacksize) {
munmap(&pt->spawn.stk, pt->stacksize);
@ -51,6 +53,9 @@ static int PosixThread(void *arg, int tid) {
((cthread_t)__get_tls())->pthread = pt;
pt->rc = pt->start_routine(pt->arg);
}
if (weaken(_pthread_key_destruct)) {
weaken(_pthread_key_destruct)(0);
}
if (atomic_load_explicit(&pt->status, memory_order_acquire) ==
kPosixThreadDetached) {
atomic_store_explicit(&pt->status, kPosixThreadZombie,
@ -71,18 +76,18 @@ static int PosixThread(void *arg, int tid) {
* pthread_create() - Standard
* Abstraction
*
* _spawn() - Cosmopolitan
* Abstraction
*
* clone() - Polyfill
*
* - Kernel
* Interfaces
* sys_clone tfork
*
* CreateThread
* bsdthread_create thr_new
*
* - Kernel
* Interfaces
* sys_clone tfork
* CreateThread
*
* bsdthread_create thr_new
*
*
* _lwp_create
*
*
* @param thread if non-null is used to output the thread id
* upon successful completion
@ -96,13 +101,14 @@ static int PosixThread(void *arg, int tid) {
* @raise EINVAL if `attr` was supplied and had unnaceptable data
* @raise EPERM if scheduling policy was requested and user account
* isn't authorized to use it
* @threadsafe
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg) {
int rc, e = errno;
struct PosixThread *pt;
pthread_attr_t default_attr;
pthread_zombies_decimate();
_pthread_zombies_decimate();
// default attributes
if (!attr) {
@ -140,7 +146,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
pt->stacksize = attr->stacksize;
} else {
rc = errno;
pthread_free(pt);
_pthread_free(pt);
errno = e;
if (rc == EINVAL || rc == EOVERFLOW) {
return EINVAL;
@ -170,7 +176,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
}
}
// save the attributes for descriptive purposes
// we only need to save this to support pthread_getattr_np()
pt->attr = *attr;
// set initial status
@ -180,10 +186,10 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
break;
case PTHREAD_CREATE_DETACHED:
pt->status = kPosixThreadDetached;
pthread_zombies_add(pt);
_pthread_zombies_add(pt);
break;
default:
pthread_free(pt);
_pthread_free(pt);
return EINVAL;
}
@ -195,7 +201,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
CLONE_CHILD_CLEARTID,
pt, &pt->spawn.ptid, pt->spawn.tib, pt->spawn.ctid) == -1) {
rc = errno;
pthread_free(pt);
_pthread_free(pt);
errno = e;
if (rc == EINVAL) {
return EINVAL;

View file

@ -34,18 +34,25 @@ int pthread_detach(pthread_t thread) {
for (;;) {
status = atomic_load_explicit(&pt->status, memory_order_relaxed);
if (status == kPosixThreadDetached || status == kPosixThreadZombie) {
// these two states indicate the thread was already detached, in
// which case it's already listed under _pthread_zombies.
break;
} else if (status == kPosixThreadTerminated) {
pthread_wait(pt);
pthread_free(pt);
// thread was joinable and finished running. since pthread_join
// won't be called, it's safe to free the thread resources now.
_pthread_wait(pt);
_pthread_free(pt);
break;
} else if (status == kPosixThreadJoinable &&
atomic_compare_exchange_weak_explicit(
&pt->status, &status, kPosixThreadDetached,
memory_order_acquire, memory_order_relaxed)) {
pthread_zombies_add(pt);
} else if (status == kPosixThreadJoinable) {
if (atomic_compare_exchange_weak_explicit(
&pt->status, &status, kPosixThreadDetached, memory_order_acquire,
memory_order_relaxed)) {
_pthread_zombies_add(pt);
break;
}
} else {
notpossible;
}
}
return 0;
}

View file

@ -24,10 +24,22 @@
/**
* Terminates current POSIX thread.
*
* If this function is called from the main thread, or a thread created
* with clone() or _spawn(), then this function is the same as _Exit1()
* in which case `rc` is coerced to a `uint8_t` exit status, which will
* only be reported to the parent process on Linux, FreeBSD and Windows
*
* @param rc is reported later to pthread_join()
* @threadsafe
* @noreturn
*/
void pthread_exit(void *rc) {
wontreturn void pthread_exit(void *rc) {
struct PosixThread *pt;
pt = ((cthread_t)__get_tls())->pthread;
if ((pt = ((cthread_t)__get_tls())->pthread)) {
pt->rc = rc;
longjmp(pt->exiter, 1);
} else {
_Exit1((int)(intptr_t)rc);
}
}

View file

@ -32,14 +32,15 @@
*/
int pthread_join(pthread_t thread, void **value_ptr) {
struct PosixThread *pt = thread;
if (pt->status == kPosixThreadDetached) {
if (pt->status == kPosixThreadDetached || //
pt->status == kPosixThreadZombie) {
assert(!"badjoin");
return EDEADLK;
}
pthread_wait(pt);
_pthread_wait(pt);
if (value_ptr) {
*value_ptr = pt->rc;
}
pthread_free(pt);
_pthread_free(pt);
return 0;
}

View file

@ -41,7 +41,7 @@ int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) {
}
static textexit void _pthread_key_atexit(void) {
_pthread_key_destruct(_pthread_keys);
_pthread_key_destruct(0);
}
__attribute__((__constructor__)) static textstartup void _pthread_key_init() {

View file

@ -25,6 +25,7 @@ void _pthread_key_destruct(void *key[PTHREAD_KEYS_MAX]) {
uint64_t x;
void *value;
pthread_key_dtor dtor;
if (!key) key = _pthread_keys;
StartOver:
for (i = 0; i < (PTHREAD_KEYS_MAX + 63) / 64; ++i) {
x = _pthread_key_usage[i];

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/atomic.h"
#include "libc/intrin/pthread.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
#include "libc/thread/posixthread.internal.h"
@ -29,46 +30,46 @@
static struct Zombie {
struct Zombie *next;
struct PosixThread *pt;
} * pthread_zombies;
} * _pthread_zombies;
void pthread_zombies_add(struct PosixThread *pt) {
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);
z->next = atomic_load(&_pthread_zombies);
for (;;) {
if (atomic_compare_exchange_weak(&pthread_zombies, &z->next, z)) {
if (atomic_compare_exchange_weak(&_pthread_zombies, &z->next, z)) {
break;
}
}
}
}
static void pthread_zombies_collect(struct Zombie *z) {
pthread_wait(z->pt);
pthread_free(z->pt);
static void _pthread_zombies_collect(struct Zombie *z) {
_pthread_wait(z->pt);
_pthread_free(z->pt);
free(z);
}
void pthread_zombies_decimate(void) {
void _pthread_zombies_decimate(void) {
struct Zombie *z;
while ((z = atomic_load(&pthread_zombies)) &&
while ((z = atomic_load_explicit(&_pthread_zombies, memory_order_relaxed)) &&
atomic_load(&z->pt->status) == kPosixThreadZombie) {
if (atomic_compare_exchange_strong(&pthread_zombies, &z, z->next)) {
pthread_zombies_collect(z);
if (atomic_compare_exchange_weak(&_pthread_zombies, &z, z->next)) {
_pthread_zombies_collect(z);
}
}
}
void pthread_zombies_harvest(void) {
void _pthread_zombies_harvest(void) {
struct Zombie *z;
while ((z = atomic_load(&pthread_zombies))) {
if (atomic_compare_exchange_weak(&pthread_zombies, &z, z->next)) {
pthread_zombies_collect(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);
}
}
}
__attribute__((__constructor__)) static void init(void) {
atexit(pthread_zombies_harvest);
atexit(_pthread_zombies_harvest);
}

View file

@ -17,12 +17,15 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/intrin/pthread.h"
#include "libc/mem/mem.h"
#include "libc/runtime/stack.h"
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/thread.h"
#if 0
static void *Increment(void *arg) {
ASSERT_EQ(gettid(), pthread_getthreadid_np());
return (void *)((uintptr_t)arg + 1);
@ -98,3 +101,17 @@ TEST(pthread_detach, testCustomStack_withReallySmallSize) {
ASSERT_EQ(0, pthread_join(id, 0));
free(stk);
}
#endif
TEST(pthread_exit, mainThreadWorks) {
// _Exit1() can't set process exit code on XNU/NetBSD/OpenBSD.
if (IsLinux() || IsFreebsd() || IsWindows()) {
SPAWN(fork);
pthread_exit((void *)2);
EXITS(2);
} else {
SPAWN(fork);
pthread_exit((void *)0);
EXITS(0);
}
}