Make futexes cancellable by pthreads

This commit is contained in:
Justine Tunney 2022-11-04 18:19:05 -07:00
parent 2278327eba
commit 022536cab6
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
101 changed files with 627 additions and 391 deletions

View file

@ -48,7 +48,6 @@ struct _umtx_time {
uint32_t _clockid;
};
int sys_umtx_op(void *, int, unsigned long, void *, void *);
int sys_umtx_timedwait_uint(_Atomic(int) *, int, bool, const struct timespec *);
COSMOPOLITAN_C_END_

View file

@ -15,8 +15,8 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
// LEGAL TRANSITIONS ┌──> TERMINATED
// pthread_create ─┬─> JOINABLE ─┴┬─> DETACHED ──> ZOMBIE
// LEGAL TRANSITIONS ┌──> TERMINATED ─┐
// pthread_create ─┬─> JOINABLE ─┴┬─> DETACHED ───┴─> ZOMBIE
// └──────────────┘
enum PosixThreadStatus {
@ -47,6 +47,8 @@ enum PosixThreadStatus {
//
// - kPosixThreadTerminated -> _pthread_free() will happen when
// pthread_join() is called by the user.
// - kPosixThreadTerminated -> kPosixThreadZombie will happen when
// pthread_detach() is called by the user.
kPosixThreadTerminated,
// this is a detached thread that terminated.
@ -83,10 +85,10 @@ extern _Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX] hidden;
int _pthread_atfork(atfork_f, atfork_f, atfork_f) hidden;
int _pthread_reschedule(struct PosixThread *) hidden;
int _pthread_setschedparam_freebsd(int, int, const struct sched_param *) hidden;
int _pthread_wait(struct PosixThread *) hidden;
void _pthread_free(struct PosixThread *) hidden;
void _pthread_cleanup(struct PosixThread *) hidden;
void _pthread_ungarbage(void) hidden;
void _pthread_wait(struct PosixThread *) hidden;
void _pthread_zombies_add(struct PosixThread *) hidden;
void _pthread_zombies_purge(void) hidden;
void _pthread_zombies_decimate(void) hidden;

View file

@ -65,8 +65,11 @@ void _pthread_onfork_parent(void) {
}
void _pthread_onfork_child(void) {
pthread_mutexattr_t attr;
extern pthread_mutex_t __mmi_lock_obj;
bzero(&__mmi_lock_obj, sizeof(__mmi_lock_obj));
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&__mmi_lock_obj, &attr);
__kmalloc_unlock();
_pthread_onfork(2);
}

View file

@ -21,8 +21,10 @@
/**
* Destroys pthread attributes.
*
* @return 0 on success, or errno on error
*/
int pthread_attr_destroy(pthread_attr_t *attr) {
errno_t pthread_attr_destroy(pthread_attr_t *attr) {
memset(attr, -1, sizeof(*attr));
return 0;
}

View file

@ -26,7 +26,8 @@
* - `PTHREAD_CREATE_DETACHED`
* @return 0 on success, or error on failure
*/
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) {
errno_t pthread_attr_getdetachstate(const pthread_attr_t *attr,
int *detachstate) {
*detachstate = attr->__detachstate;
return 0;
}

View file

@ -21,8 +21,8 @@
/**
* Returns thread inherit schedule attribute.
*/
int pthread_attr_getinheritsched(const pthread_attr_t *attr,
int *inheritsched) {
errno_t pthread_attr_getinheritsched(const pthread_attr_t *attr,
int *inheritsched) {
*inheritsched = attr->__inheritsched;
return 0;
}

View file

@ -21,8 +21,8 @@
/**
* Gets thread scheduler parameter attribute.
*/
int pthread_attr_getschedparam(const pthread_attr_t *attr,
struct sched_param *param) {
errno_t pthread_attr_getschedparam(const pthread_attr_t *attr,
struct sched_param *param) {
*param = (struct sched_param){attr->__schedparam};
return 0;
}

View file

@ -21,7 +21,7 @@
/**
* Gets thread scheduler policy attribute
*/
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy) {
errno_t pthread_attr_getschedpolicy(const pthread_attr_t *attr, int *policy) {
*policy = attr->__schedpolicy;
return 0;
}

View file

@ -23,7 +23,8 @@
*
* @return 0 on success, or errno on error
*/
int pthread_attr_getscope(const pthread_attr_t *attr, int *contentionscope) {
errno_t pthread_attr_getscope(const pthread_attr_t *attr,
int *contentionscope) {
*contentionscope = attr->__contentionscope;
return 0;
}

View file

@ -34,7 +34,7 @@
* @return 0 on success, or error on failure
* @raises EINVAL if `detachstate` is invalid
*/
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) {
errno_t pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) {
switch (detachstate) {
case PTHREAD_CREATE_JOINABLE:
case PTHREAD_CREATE_DETACHED:

View file

@ -40,8 +40,8 @@
* @see sched_get_priority_max()
* @see sched_setparam()
*/
int pthread_attr_setschedparam(pthread_attr_t *attr,
const struct sched_param *param) {
errno_t pthread_attr_setschedparam(pthread_attr_t *attr,
const struct sched_param *param) {
if (!param) return EINVAL;
attr->__schedparam = param->sched_priority;
return 0;

View file

@ -41,7 +41,7 @@
* supported (Linux), otherwise it's treated as `SCHED_OTHER`
* @see sched_setscheduler()
*/
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) {
errno_t pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) {
attr->__schedpolicy = policy;
return 0;
}

View file

@ -30,7 +30,7 @@
* @raise ENOTSUP if `contentionscope` isn't supported on host OS
* @raise EINVAL if `contentionscope` was invalid
*/
int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope) {
errno_t pthread_attr_setscope(pthread_attr_t *attr, int contentionscope) {
switch (contentionscope) {
case PTHREAD_SCOPE_SYSTEM:
attr->__contentionscope = contentionscope;

View file

@ -26,7 +26,7 @@
* @return 0 on success, or error on failure
* @raise EINVAL if threads are still inside the barrier
*/
int pthread_barrier_destroy(pthread_barrier_t *barrier) {
errno_t pthread_barrier_destroy(pthread_barrier_t *barrier) {
if (barrier->_nsync) {
nsync_counter_free(barrier->_nsync);
barrier->_nsync = 0;

View file

@ -30,8 +30,9 @@
* @raise EINVAL if `count` isn't greater than zero
* @raise ENOMEM if insufficient memory exists
*/
int pthread_barrier_init(pthread_barrier_t *barrier,
const pthread_barrierattr_t *attr, unsigned count) {
errno_t pthread_barrier_init(pthread_barrier_t *barrier,
const pthread_barrierattr_t *attr,
unsigned count) {
nsync_counter c;
if (!count) return EINVAL;
if (!(c = nsync_counter_new(count))) return ENOMEM;

View file

@ -16,15 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/thread.h"
#include "libc/str/str.h"
#include "libc/thread/thread.h"
/**
* Destroys barrier attributes.
*
* @return 0 on success, or error on failure
*/
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr) {
errno_t pthread_barrierattr_destroy(pthread_barrierattr_t *attr) {
memset(attr, -1, sizeof(*attr));
return 0;
}

View file

@ -26,8 +26,8 @@
* - `PTHREAD_PROCESS_SHARED` (unsupported)
* @return 0 on success, or error on failure
*/
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *attr,
int *pshared) {
errno_t pthread_barrierattr_getpshared(const pthread_barrierattr_t *attr,
int *pshared) {
*pshared = *attr;
return 0;
}

View file

@ -23,7 +23,7 @@
*
* @return 0 on success, or error on failure
*/
int pthread_barrierattr_init(pthread_barrierattr_t *attr) {
errno_t pthread_barrierattr_init(pthread_barrierattr_t *attr) {
*attr = 0;
return 0;
}

View file

@ -28,7 +28,8 @@
* @return 0 on success, or error on failure
* @raises EINVAL if `pshared` is invalid
*/
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared) {
errno_t pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,
int pshared) {
switch (pshared) {
case PTHREAD_PROCESS_PRIVATE:
*attr = pshared;

View file

@ -24,6 +24,7 @@
#include "libc/calls/ucontext.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/kprintf.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/sa.h"
@ -85,12 +86,12 @@ static void ListenForSigCancel(void) {
* `writev`, `pwrite`, `pwritev`, `accept`, `connect`, `recvmsg`,
* `sendmsg`, `recv`, `send`, `tcdrain`, `clock_nanosleep`, `fsync`,
* `fdatasync`, `fcntl(F_SETLKW)`, `epoll`, `sigsuspend`, `msync`,
* `wait4`, `getrandom`, `pthread_cond_timedwait` are most cancellation
* points, plus many userspace libraries that call the above functions,
* unless they're using pthread_setcancelstate() to temporarily disable
* the cancellation mechanism. Some userspace functions, e.g. system()
* and popen() will eagerly call pthread_testcancel_np() to help avoid
* the potential for resource leaks later on.
* `wait4`, `getrandom`, `pthread_cond_timedwait`, and `sem_timedwait`
* are most cancellation points, plus many userspace libraries that call
* the above functions, unless they're using pthread_setcancelstate() to
* temporarily disable the cancellation mechanism. Some userspace
* functions, e.g. system() will eagerly call pthread_testcancel_np() to
* help avoid the potential for resource leaks later on.
*
* It's possible to put a thread in asynchronous cancellation mode using
* pthread_setcanceltype(), thus allowing a cancellation to occur at any
@ -110,7 +111,7 @@ static void ListenForSigCancel(void) {
* @return 0 on success, or errno on error
* @raise ESRCH if thread isn't alive
*/
int pthread_cancel(pthread_t thread) {
errno_t pthread_cancel(pthread_t thread) {
int e, rc, tid;
static bool once;
struct PosixThread *pt;
@ -124,9 +125,9 @@ int pthread_cancel(pthread_t thread) {
default:
break;
}
atomic_store_explicit(&pt->cancelled, 1, memory_order_release);
atomic_exchange_explicit(&pt->cancelled, 1, memory_order_release);
if (thread == __get_tls()->tib_pthread) {
if (!(pt->flags & PT_NOCANCEL) && (pt->flags & PT_ASYNC)) {
if (!(pt->flags & (PT_NOCANCEL | PT_MASKED)) && (pt->flags & PT_ASYNC)) {
pthread_exit(PTHREAD_CANCELED);
}
return 0;
@ -183,7 +184,7 @@ void pthread_testcancel(void) {
* @return 0 if not cancelled or cancellation is blocked or `ECANCELED`
* in masked mode when the calling thread has been cancelled
*/
int pthread_testcancel_np(void) {
errno_t pthread_testcancel_np(void) {
int rc;
struct PosixThread *pt;
if (!__tls_enabled) return 0;

View file

@ -1,31 +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/assert.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
void _pthread_cleanup_pop(struct _pthread_cleanup_buffer *cb, int execute) {
struct PosixThread *pt = (struct PosixThread *)__get_tls()->tib_pthread;
_unassert(cb == pt->cleanup);
pt->cleanup = cb->__prev;
if (execute) {
cb->__routine(cb->__arg);
}
}

View file

@ -1,29 +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/posixthread.internal.h"
#include "libc/thread/thread.h"
void _pthread_cleanup_push(struct _pthread_cleanup_buffer *cb,
void (*routine)(void *), void *arg) {
struct PosixThread *pt = (struct PosixThread *)__get_tls()->tib_pthread;
cb->__routine = routine;
cb->__arg = arg;
cb->__prev = pt->cleanup;
pt->cleanup = cb;
}

View file

@ -25,7 +25,7 @@
* @return 0 on success, or error number on failure
* @raise EINVAL if threads are still waiting on condition
*/
int pthread_cond_destroy(pthread_cond_t *cond) {
errno_t pthread_cond_destroy(pthread_cond_t *cond) {
memset(cond, -1, sizeof(*cond));
return 0;
}

View file

@ -24,7 +24,8 @@
* @param attr may be null
* @return 0 on success, or error number on failure
*/
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) {
errno_t pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr) {
*cond = (pthread_cond_t){0};
return 0;
}

View file

@ -41,8 +41,9 @@
* @raise EPERM if `mutex` is `PTHREAD_MUTEX_ERRORCHECK` and the lock
* isn't owned by the current thread
* @raise EINVAL if `0 abstime->tv_nsec < 1000000000` wasn't the case
* @see pthread_cond_broadcast
* @see pthread_cond_signal
* @raise ECANCELED if calling thread was cancelled in masked mode
* @see pthread_cond_broadcast()
* @see pthread_cond_signal()
* @cancellationpoint
*/
errno_t pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,

View file

@ -31,6 +31,7 @@
*
* @param mutex needs to be held by thread when calling this function
* @return 0 on success, or errno on error
* @raise ECANCELED if calling thread was cancelled in masked mode
* @raise EPERM if `mutex` is `PTHREAD_MUTEX_ERRORCHECK` and the lock
* isn't owned by the current thread
* @see pthread_cond_timedwait

View file

@ -16,15 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/thread.h"
#include "libc/str/str.h"
#include "libc/thread/thread.h"
/**
* Destroys condition attributes.
*
* @return 0 on success, or error on failure
*/
int pthread_condattr_destroy(pthread_condattr_t *attr) {
errno_t pthread_condattr_destroy(pthread_condattr_t *attr) {
memset(attr, -1, sizeof(*attr));
return 0;
}

View file

@ -26,7 +26,8 @@
* - `PTHREAD_PROCESS_SHARED`
* @return 0 on success, or error on failure
*/
int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared) {
errno_t pthread_condattr_getpshared(const pthread_condattr_t *attr,
int *pshared) {
*pshared = *attr;
return 0;
}

View file

@ -23,7 +23,7 @@
*
* @return 0 on success, or error on failure
*/
int pthread_condattr_init(pthread_condattr_t *attr) {
errno_t pthread_condattr_init(pthread_condattr_t *attr) {
*attr = 0;
return 0;
}

View file

@ -28,7 +28,7 @@
* @return 0 on success, or error on failure
* @raises EINVAL if `pshared` is invalid
*/
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared) {
errno_t pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared) {
switch (pshared) {
case PTHREAD_PROCESS_PRIVATE:
*attr = pshared;

View file

@ -59,8 +59,8 @@ STATIC_YOINK("_pthread_atfork");
#define MAP_ANON_OPENBSD 0x1000
#define MAP_STACK_OPENBSD 0x4000
void _pthread_wait(struct PosixThread *pt) {
_wait0(&pt->tib->tib_tid);
errno_t _pthread_wait(struct PosixThread *pt) {
return _wait0(&pt->tib->tib_tid);
}
void _pthread_free(struct PosixThread *pt) {

View file

@ -32,36 +32,29 @@
* @returnserrno
* @threadsafe
*/
int pthread_detach(pthread_t thread) {
errno_t pthread_detach(pthread_t thread) {
struct PosixThread *pt;
enum PosixThreadStatus status;
if (!(pt = (struct PosixThread *)thread)) {
return EINVAL;
}
enum PosixThreadStatus status, transition;
if (!(pt = (struct PosixThread *)thread)) return EINVAL;
for (;;) {
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 == kPosixThreadTerminated) {
// thread was joinable and finished running. since pthread_join
// won't be called, it's safe to free the thread resources now.
// POSIX says this could be reported as ESRCH but then our test
// code would be less elegant in order for it to avoid flaking.
_pthread_wait(pt);
_pthread_free(pt);
break;
} else if (status == kPosixThreadJoinable) {
if (atomic_compare_exchange_weak_explicit(
&pt->status, &status, kPosixThreadDetached, memory_order_release,
memory_order_relaxed)) {
_pthread_zombies_add(pt);
break;
}
transition = kPosixThreadDetached;
} else if (status == kPosixThreadTerminated) {
transition = kPosixThreadZombie;
} else {
notpossible;
}
if (atomic_compare_exchange_weak_explicit(&pt->status, &status, transition,
memory_order_release,
memory_order_relaxed)) {
_pthread_zombies_add(pt);
break;
}
}
return 0;
}

View file

@ -47,7 +47,7 @@
* @return 0 on success, or errno on error
* @raise ENOMEM is listed as a possible result by LSB 5.0
*/
int pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) {
errno_t pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) {
struct PosixThread *pt = (struct PosixThread *)thread;
memcpy(attr, &pt->attr, sizeof(pt->attr));
switch (atomic_load_explicit(&pt->status, memory_order_relaxed)) {

View file

@ -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/syscall-sysv.internal.h"
#include "libc/dce.h"
@ -28,23 +29,7 @@
#include "libc/sysv/consts/pr.h"
#include "libc/thread/posixthread.internal.h"
/**
* Gets name of thread registered with system, e.g.
*
* char name[64];
* pthread_getname_np(thread, name, sizeof(name));
*
* If the thread doesn't have a name, then empty string is returned.
* This implementation guarantees `buf` is always modified, even on
* error, and will always be nul-terminated. If `size` is 0 then this
* function returns 0. Your `buf` is also chomped to remove newlines.
*
* @return 0 on success, or errno on error
* @raise ERANGE if `size` wasn't large enough, in which case your
* result will still be returned truncated if possible
* @raise ENOSYS on MacOS, Windows, FreeBSD, and OpenBSD
*/
errno_t pthread_getname_np(pthread_t thread, char *name, size_t size) {
static errno_t pthread_getname_impl(pthread_t thread, char *name, size_t size) {
int fd, rc, tid, len, e = errno;
if (!size) return 0;
@ -113,3 +98,27 @@ errno_t pthread_getname_np(pthread_t thread, char *name, size_t size) {
return ENOSYS;
}
}
/**
* Gets name of thread registered with system, e.g.
*
* char name[64];
* pthread_getname_np(thread, name, sizeof(name));
*
* If the thread doesn't have a name, then empty string is returned.
* This implementation guarantees `buf` is always modified, even on
* error, and will always be nul-terminated. If `size` is 0 then this
* function returns 0. Your `buf` is also chomped to remove newlines.
*
* @return 0 on success, or errno on error
* @raise ERANGE if `size` wasn't large enough, in which case your
* result will still be returned truncated if possible
* @raise ENOSYS on MacOS, Windows, FreeBSD, and OpenBSD
*/
errno_t pthread_getname_np(pthread_t thread, char *name, size_t size) {
errno_t rc;
BLOCK_CANCELLATIONS;
rc = pthread_getname_impl(thread, name, size);
ALLOW_CANCELLATIONS;
return rc;
}

View file

@ -22,8 +22,8 @@
/**
* Gets most recently set scheduling of thread.
*/
int pthread_getschedparam(pthread_t thread, int *policy,
struct sched_param *param) {
errno_t pthread_getschedparam(pthread_t thread, int *policy,
struct sched_param *param) {
struct PosixThread *pt = (struct PosixThread *)thread;
*policy = pt->attr.__schedpolicy;
*param = (struct sched_param){pt->attr.__schedparam};

View file

@ -27,14 +27,16 @@
* @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 with error
* @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
*/
int pthread_join(pthread_t thread, void **value_ptr) {
errno_t pthread_join(pthread_t thread, void **value_ptr) {
errno_t rc;
struct PosixThread *pt;
if (thread == __get_tls()->tib_pthread) {
return EDEADLK;
@ -44,7 +46,9 @@ int pthread_join(pthread_t thread, void **value_ptr) {
pt->status == kPosixThreadDetached) {
return EINVAL;
}
_pthread_wait(pt);
if ((rc = _pthread_wait(pt))) {
return rc;
}
if (value_ptr) {
*value_ptr = pt->rc;
}

View file

@ -31,7 +31,7 @@
* @raise EPERM if permission was denied
* @asyncsignalsafe
*/
int pthread_kill(pthread_t thread, int sig) {
errno_t pthread_kill(pthread_t thread, int sig) {
int rc, e = errno;
struct PosixThread *pt = (struct PosixThread *)thread;
if (!tkill(pt->tid, sig)) {

View file

@ -25,7 +25,7 @@
* @return 0 on success, or error number on failure
* @raise EINVAL if mutex is locked in our implementation
*/
int pthread_mutex_destroy(pthread_mutex_t *mutex) {
errno_t pthread_mutex_destroy(pthread_mutex_t *mutex) {
memset(mutex, -1, sizeof(*mutex));
return 0;
}

View file

@ -16,14 +16,14 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/thread.h"
#include "libc/str/str.h"
#include "libc/thread/thread.h"
/**
* Destroys mutex attr.
* @return 0 on success, or error number on failure
*/
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr) {
errno_t pthread_mutexattr_destroy(pthread_mutexattr_t *attr) {
memset(attr, -1, sizeof(*attr));
return 0;
}

View file

@ -26,8 +26,8 @@
* - `PTHREAD_PROCESS_SHARED`
* @return 0 on success, or error on failure
*/
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr,
int *pshared) {
errno_t pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr,
int *pshared) {
*pshared = attr->_pshared;
return 0;
}

View file

@ -27,7 +27,7 @@
* - `PTHREAD_MUTEX_ERRORCHECK`
* @return 0 on success, or error on failure
*/
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type) {
errno_t pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type) {
*type = attr->_type;
return 0;
}

View file

@ -22,7 +22,7 @@
* Initializes mutex attr.
* @return 0 on success, or error number on failure
*/
int pthread_mutexattr_init(pthread_mutexattr_t *attr) {
errno_t pthread_mutexattr_init(pthread_mutexattr_t *attr) {
*attr = (pthread_mutexattr_t){0};
return 0;
}

View file

@ -28,7 +28,7 @@
* @return 0 on success, or error on failure
* @raises EINVAL if `pshared` is invalid
*/
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared) {
errno_t pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared) {
switch (pshared) {
case PTHREAD_PROCESS_SHARED:
case PTHREAD_PROCESS_PRIVATE:

View file

@ -30,7 +30,7 @@
* @return 0 on success, or error on failure
* @raises EINVAL if `type` is invalid
*/
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) {
errno_t pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) {
switch (type) {
case PTHREAD_MUTEX_NORMAL:
case PTHREAD_MUTEX_RECURSIVE:

View file

@ -23,7 +23,7 @@
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
int _pthread_reschedule(struct PosixThread *pt) {
errno_t _pthread_reschedule(struct PosixThread *pt) {
int rc, e = errno;
int policy = pt->attr.__schedpolicy;
struct sched_param param = {pt->attr.__schedparam};

View file

@ -25,7 +25,7 @@
* @return 0 on success, or error number on failure
* @raise EINVAL if any threads still hold the lock
*/
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) {
errno_t pthread_rwlock_destroy(pthread_rwlock_t *rwlock) {
memset(rwlock, -1, sizeof(*rwlock));
return 0;
}

View file

@ -24,8 +24,8 @@
* @param attr may be null
* @return 0 on success, or error number on failure
*/
int pthread_rwlock_init(pthread_rwlock_t *rwlock,
const pthread_rwlockattr_t *attr) {
errno_t pthread_rwlock_init(pthread_rwlock_t *rwlock,
const pthread_rwlockattr_t *attr) {
*rwlock = (pthread_rwlock_t){0};
return 0;
}

View file

@ -16,15 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/thread/thread.h"
#include "libc/str/str.h"
#include "libc/thread/thread.h"
/**
* Destroys read-write lock attributes.
*
* @return 0 on success, or error on failure
*/
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr) {
errno_t pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr) {
memset(attr, -1, sizeof(*attr));
return 0;
}

View file

@ -26,8 +26,8 @@
* - `PTHREAD_PROCESS_SHARED` (unsupported)
* @return 0 on success, or error on failure
*/
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr,
int *pshared) {
errno_t pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr,
int *pshared) {
*pshared = *attr;
return 0;
}

View file

@ -23,7 +23,7 @@
*
* @return 0 on success, or error on failure
*/
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr) {
errno_t pthread_rwlockattr_init(pthread_rwlockattr_t *attr) {
*attr = 0;
return 0;
}

View file

@ -28,7 +28,7 @@
* @return 0 on success, or error on failure
* @raises EINVAL if `pshared` is invalid
*/
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared) {
errno_t pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared) {
switch (pshared) {
case PTHREAD_PROCESS_PRIVATE:
*attr = pshared;

View file

@ -1,72 +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/errno.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#include "libc/thread/tls.h"
/**
* Sets cancelability state.
*
* This function may be used to temporarily disable cancellation for the
* calling thread, which is necessary in cases when a @cancellationpoint
* function is invoked from an @asyncsignalsafe function.
*
* Cosmopolitan Libc supports the Musl Libc `PTHREAD_CANCEL_MASKED`
* non-POSIX extension. Any thread may use this setting, in which case
* the thread won't be abruptly destroyed upon a cancellation and have
* its stack unwound; instead, the thread will encounter an `ECANCELED`
* errno the next time it calls a cancellation point.
*
* @param state may be one of:
* - `PTHREAD_CANCEL_ENABLE` (default)
* - `PTHREAD_CANCEL_DISABLE`
* - `PTHREAD_CANCEL_MASKED`
* @param oldstate optionally receives old value
* @return 0 on success, or errno on error
* @raise EINVAL if `state` has bad value
* @asyncsignalsafe
*/
int pthread_setcancelstate(int state, int *oldstate) {
struct PosixThread *pt;
switch (state) {
case PTHREAD_CANCEL_ENABLE:
case PTHREAD_CANCEL_DISABLE:
case PTHREAD_CANCEL_MASKED:
pt = (struct PosixThread *)__get_tls()->tib_pthread;
if (oldstate) {
if (pt->flags & PT_NOCANCEL) {
*oldstate = PTHREAD_CANCEL_DISABLE;
} else if (pt->flags & PT_MASKED) {
*oldstate = PTHREAD_CANCEL_MASKED;
} else {
*oldstate = PTHREAD_CANCEL_ENABLE;
}
}
pt->flags &= ~(PT_NOCANCEL | PT_MASKED);
if (state == PTHREAD_CANCEL_MASKED) {
pt->flags |= PT_MASKED;
} else if (state == PTHREAD_CANCEL_DISABLE) {
pt->flags |= PT_NOCANCEL;
}
return 0;
default:
return EINVAL;
}
}

View file

@ -32,7 +32,7 @@
* @raise EINVAL if `type` has bad value
* @see pthread_cancel() for docs
*/
int pthread_setcanceltype(int type, int *oldtype) {
errno_t pthread_setcanceltype(int type, int *oldtype) {
struct PosixThread *pt;
switch (type) {
case PTHREAD_CANCEL_DEFERRED:

View file

@ -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/syscall-sysv.internal.h"
#include "libc/dce.h"
@ -28,30 +29,7 @@
#include "libc/sysv/consts/pr.h"
#include "libc/thread/posixthread.internal.h"
/**
* Registers custom name of thread with system, e.g.
*
* void *worker(void *arg) {
* pthread_setname_np(pthread_self(), "justine");
* pause();
* return 0;
* }
*
* int main(int argc, char *argv[]) {
* pthread_t id;
* pthread_create(&id, 0, worker, 0);
* pthread_join(id, 0);
* }
*
* ProTip: The `htop` software is good at displaying thread names.
*
* @return 0 on success, or errno on error
* @raise ERANGE if length of `name` exceeded system limit, in which
* case the name may have still been set with os using truncation
* @raise ENOSYS on MacOS, Windows, and OpenBSD
* @see pthread_getname_np()
*/
errno_t pthread_setname_np(pthread_t thread, const char *name) {
static errno_t pthread_setname_impl(pthread_t thread, const char *name) {
char path[128], *p;
int fd, rc, tid, len, e = errno;
@ -113,3 +91,34 @@ errno_t pthread_setname_np(pthread_t thread, const char *name) {
return ENOSYS;
}
}
/**
* Registers custom name of thread with system, e.g.
*
* void *worker(void *arg) {
* pthread_setname_np(pthread_self(), "justine");
* pause();
* return 0;
* }
*
* int main(int argc, char *argv[]) {
* pthread_t id;
* pthread_create(&id, 0, worker, 0);
* pthread_join(id, 0);
* }
*
* ProTip: The `htop` software is good at displaying thread names.
*
* @return 0 on success, or errno on error
* @raise ERANGE if length of `name` exceeded system limit, in which
* case the name may have still been set with os using truncation
* @raise ENOSYS on MacOS, Windows, and OpenBSD
* @see pthread_getname_np()
*/
errno_t pthread_setname_np(pthread_t thread, const char *name) {
errno_t rc;
BLOCK_CANCELLATIONS;
rc = pthread_setname_impl(thread, name);
ALLOW_CANCELLATIONS;
return rc;
}

View file

@ -41,8 +41,8 @@
* @see sched_get_priority_max()
* @see sched_setscheduler()
*/
int pthread_setschedparam(pthread_t thread, int policy,
const struct sched_param *param) {
errno_t pthread_setschedparam(pthread_t thread, int policy,
const struct sched_param *param) {
struct PosixThread *pt = (struct PosixThread *)thread;
if (!param) return EINVAL;
pt->attr.__schedpolicy = policy;

View file

@ -22,7 +22,7 @@
/**
* Sets scheduler parameter on thread.
*/
int pthread_setschedprio(pthread_t thread, int prio) {
errno_t pthread_setschedprio(pthread_t thread, int prio) {
struct PosixThread *pt = (struct PosixThread *)thread;
pt->attr.__schedparam = prio;
return _pthread_reschedule(pt);

View file

@ -25,7 +25,7 @@
* @return 0 on success, or errno on error
* @asyncsignalsafe
*/
int pthread_sigmask(int how, const sigset_t *set, sigset_t *old) {
errno_t pthread_sigmask(int how, const sigset_t *set, sigset_t *old) {
int rc, e = errno;
if (!sigprocmask(how, set, old)) {
rc = 0;

View file

@ -44,6 +44,7 @@ void _pthread_zombies_add(struct PosixThread *pt) {
}
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);

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/dce.h"
@ -195,6 +196,7 @@ sem_t *sem_open(const char *name, int oflag, ...) {
if (!(path = sem_path_np(name, pathbuf, sizeof(pathbuf)))) {
return SEM_FAILED;
}
BLOCK_CANCELLATIONS;
sem_open_init();
sem_open_lock();
if ((s = sem_open_reopen(path))) {
@ -233,6 +235,7 @@ sem_t *sem_open(const char *name, int oflag, ...) {
sem = SEM_FAILED;
}
sem_open_unlock();
ALLOW_CANCELLATIONS;
return sem;
}

View file

@ -24,6 +24,7 @@
#include "libc/limits.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/semaphore.h"
#include "libc/thread/thread.h"
#include "third_party/nsync/futex.internal.h"
static void sem_delay(int n) {
@ -51,11 +52,18 @@ static struct timespec *sem_timeout(struct timespec *memory,
}
}
static void sem_timedwait_cleanup(void *arg) {
sem_t *sem = arg;
_unassert(atomic_fetch_add_explicit(&sem->sem_waiters, -1,
memory_order_acq_rel) > 0);
}
/**
* Locks semaphore w/ deadline.
*
* @param abstime is absolute deadline or null to wait forever
* @return 0 on success, or -1 w/ errno
* @raise ECANCELED if calling thread was cancelled in masked mode
* @raise EINTR if signal was delivered instead
* @raise EDEADLK if deadlock was detected
* @raise ETIMEDOUT if deadline expired
@ -80,14 +88,16 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) {
}
_unassert(atomic_fetch_add_explicit(&sem->sem_waiters, +1,
memory_order_acquire) >= 0);
memory_order_acq_rel) >= 0);
pthread_cleanup_push(sem_timedwait_cleanup, sem);
do {
if (!(v = atomic_load_explicit(&sem->sem_value, memory_order_relaxed))) {
rc = nsync_futex_wait_(&sem->sem_value, v, sem->sem_pshared,
sem_timeout(&ts, abstime));
if (rc == -EINTR) {
rc = eintr();
if (rc == -EINTR || rc == -ECANCELED) {
errno = -rc;
rc = -1;
} else if (rc == -EAGAIN || rc == -EWOULDBLOCK) {
rc = 0;
} else if (rc == -ETIMEDOUT) {
@ -111,8 +121,7 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) {
&sem->sem_value, &v, v - 1, memory_order_acquire,
memory_order_relaxed)));
_unassert(atomic_fetch_add_explicit(&sem->sem_waiters, -1,
memory_order_release) > 0);
pthread_cleanup_pop(1);
return rc;
}

View file

@ -22,6 +22,7 @@
* Locks semaphore.
*
* @return 0 on success, or -1 w/ errno
* @raise ECANCELED if calling thread was cancelled in masked mode
* @raise EINTR if signal was delivered instead
* @raise EDEADLK if deadlock was detected
* @raise EINVAL if `sem` is invalid

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/thread/wait0.internal.h"
#include "third_party/nsync/futex.internal.h"
@ -29,10 +30,16 @@
* order to know when it's safe to free a thread's stack. This function
* uses futexes on Linux, FreeBSD, OpenBSD, and Windows. On other
* platforms this uses polling with exponential backoff.
*
* @return 0 on success, or errno on error
* @raise ECANCELED if calling thread was cancelled in masked mode
*/
void _wait0(const atomic_int *ctid) {
int x;
errno_t _wait0(const atomic_int *ctid) {
int x, rc;
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
nsync_futex_wait_(ctid, x, !IsWindows(), 0);
if (nsync_futex_wait_(ctid, x, !IsWindows(), 0) == -ECANCELED) {
return ECANCELED;
}
}
return 0;
}

View file

@ -4,7 +4,7 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
void _wait0(const atomic_int *) hidden;
errno_t _wait0(const atomic_int *) hidden;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */