mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-03-03 07:29:23 +00:00
Make some fixups to POSIX threads
This commit is contained in:
parent
de511bc71a
commit
6c323383e5
12 changed files with 168 additions and 69 deletions
|
@ -593,7 +593,7 @@ typedef struct {
|
|||
|
||||
#define notpossible \
|
||||
do { \
|
||||
asm("hlt"); \
|
||||
asm("ud2"); \
|
||||
unreachable; \
|
||||
} while (0)
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -34,17 +34,24 @@ 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);
|
||||
break;
|
||||
} else if (status == kPosixThreadJoinable &&
|
||||
atomic_compare_exchange_weak_explicit(
|
||||
&pt->status, &status, kPosixThreadDetached,
|
||||
memory_order_acquire, memory_order_relaxed)) {
|
||||
pthread_zombies_add(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) {
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
pt->rc = rc;
|
||||
longjmp(pt->exiter, 1);
|
||||
if ((pt = ((cthread_t)__get_tls())->pthread)) {
|
||||
pt->rc = rc;
|
||||
longjmp(pt->exiter, 1);
|
||||
} else {
|
||||
_Exit1((int)(intptr_t)rc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue