X-Git-Url: https://git.saurik.com/apple/libpthread.git/blobdiff_plain/a0619f9c1b0bf5530b0accb349cdfa98fa5b8c02..c6e5f90c4dd303939f631da331df7b356da942e6:/src/pthread_cancelable.c diff --git a/src/pthread_cancelable.c b/src/pthread_cancelable.c index 894178c..3df57a7 100644 --- a/src/pthread_cancelable.c +++ b/src/pthread_cancelable.c @@ -60,10 +60,10 @@ #include #include #include +#include #include #include -extern int __unix_conforming; extern int _pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime, @@ -73,16 +73,27 @@ extern int __sigwait(const sigset_t *set, int *sig); extern int __pthread_sigmask(int, const sigset_t *, sigset_t *); extern int __pthread_markcancel(mach_port_t); extern int __pthread_canceled(int); +extern int __semwait_signal_nocancel(int, int, int, int, __int64_t, __int32_t); -#ifdef VARIANT_CANCELABLE -extern int __semwait_signal(int cond_sem, int mutex_sem, int timeout, int relative, __int64_t tv_sec, __int32_t tv_nsec); -#else -extern int __semwait_signal(int cond_sem, int mutex_sem, int timeout, int relative, __int64_t tv_sec, __int32_t tv_nsec) __asm__("___semwait_signal_nocancel"); -#endif PTHREAD_NOEXPORT -int _pthread_join(pthread_t thread, void **value_ptr, int conforming, - int (*_semwait_signal)(int, int, int, int, __int64_t, __int32_t)); +int _pthread_join(pthread_t thread, void **value_ptr, int conforming); + +static inline int +_pthread_conformance(void) +{ +#if __DARWIN_UNIX03 + if (__unix_conforming == 0) + __unix_conforming = 1; +#ifdef VARIANT_CANCELABLE + return PTHREAD_CONFORM_UNIX03_CANCELABLE; +#else /* !VARIANT_CANCELABLE */ + return PTHREAD_CONFORM_UNIX03_NOCANCEL; +#endif +#else /* __DARWIN_UNIX03 */ + return PTHREAD_CONFORM_DARWIN_LEGACY; +#endif /* __DARWIN_UNIX03 */ +} #ifndef VARIANT_CANCELABLE @@ -111,7 +122,7 @@ pthread_cancel(pthread_t thread) __unix_conforming = 1; #endif /* __DARWIN_UNIX03 */ - if (!_pthread_is_valid(thread, 0, NULL)) { + if (!_pthread_is_valid(thread, NULL)) { return(ESRCH); } @@ -135,15 +146,7 @@ pthread_cancel(pthread_t thread) void pthread_testcancel(void) { - pthread_t self = pthread_self(); - -#if __DARWIN_UNIX03 - if (__unix_conforming == 0) - __unix_conforming = 1; - _pthread_testcancel(self, 1); -#else /* __DARWIN_UNIX03 */ - _pthread_testcancel(self, 0); -#endif /* __DARWIN_UNIX03 */ + _pthread_testcancel(_pthread_conformance()); } #ifndef BUILDING_VARIANT /* [ */ @@ -154,68 +157,54 @@ _pthread_exit_if_canceled(int error) { if (((error & 0xff) == EINTR) && __unix_conforming && (__pthread_canceled(0) == 0)) { pthread_t self = pthread_self(); - if (self != NULL) { - self->cancel_error = error; - } + + self->cancel_error = error; + self->canceled = true; pthread_exit(PTHREAD_CANCELED); } } -PTHREAD_NOEXPORT_VARIANT -void -_pthread_testcancel(pthread_t thread, int isconforming) +static inline bool +_pthread_is_canceled(pthread_t thread) { const int flags = (PTHREAD_CANCEL_ENABLE|_PTHREAD_CANCEL_PENDING); - int state = os_atomic_load2o(thread, cancel_state, seq_cst); - if ((state & flags) == flags) { - pthread_exit(isconforming ? PTHREAD_CANCELED : 0); - } + return (state & flags) == flags; } -PTHREAD_NOEXPORT +PTHREAD_NOEXPORT_VARIANT void -_pthread_markcancel_if_canceled(pthread_t thread, mach_port_t kport) +_pthread_testcancel(int isconforming) { - const int flags = (PTHREAD_CANCEL_ENABLE|_PTHREAD_CANCEL_PENDING); - - int state = os_atomic_or2o(thread, cancel_state, - _PTHREAD_CANCEL_INITIALIZED, relaxed); - if ((state & flags) == flags && __unix_conforming) { - __pthread_markcancel(kport); + pthread_t self = pthread_self(); + if (_pthread_is_canceled(self)) { + // 4597450: begin + self->canceled = (isconforming != PTHREAD_CONFORM_DARWIN_LEGACY); + // 4597450: end + pthread_exit(isconforming ? PTHREAD_CANCELED : NULL); } } PTHREAD_NOEXPORT -void * -_pthread_get_exit_value(pthread_t thread, int conforming) +void +_pthread_markcancel_if_canceled(pthread_t thread, mach_port_t kport) { const int flags = (PTHREAD_CANCEL_ENABLE|_PTHREAD_CANCEL_PENDING); - void *value = thread->exit_value; - - if (conforming) { - int state = os_atomic_load2o(thread, cancel_state, seq_cst); - if ((state & flags) == flags) { - value = PTHREAD_CANCELED; - } + int state = os_atomic_load2o(thread, cancel_state, relaxed); + if ((state & flags) == flags && __unix_conforming) { + __pthread_markcancel(kport); } - return value; } /* When a thread exits set the cancellation state to DISABLE and DEFERRED */ PTHREAD_NOEXPORT void -_pthread_setcancelstate_exit(pthread_t thread, void *value_ptr, int conforming) +_pthread_setcancelstate_exit(pthread_t thread, void *value_ptr) { _pthread_update_cancel_state(thread, _PTHREAD_CANCEL_STATE_MASK | _PTHREAD_CANCEL_TYPE_MASK, PTHREAD_CANCEL_DISABLE | PTHREAD_CANCEL_DEFERRED); - if (value_ptr == PTHREAD_CANCELED) { - _PTHREAD_LOCK(thread->lock); - thread->detached |= _PTHREAD_WASCANCEL; // 4597450 - _PTHREAD_UNLOCK(thread->lock); - } } #endif /* !BUILDING_VARIANT ] */ @@ -227,30 +216,30 @@ PTHREAD_ALWAYS_INLINE static inline int _pthread_setcancelstate_internal(int state, int *oldstateptr, int conforming) { - pthread_t self; + pthread_t self = pthread_self(); switch (state) { - case PTHREAD_CANCEL_ENABLE: - if (conforming) { - __pthread_canceled(1); - } - break; - case PTHREAD_CANCEL_DISABLE: - if (conforming) { - __pthread_canceled(2); - } - break; - default: - return EINVAL; + case PTHREAD_CANCEL_ENABLE: + if (conforming) { + __pthread_canceled(1); + } + break; + case PTHREAD_CANCEL_DISABLE: + if (conforming) { + __pthread_canceled(2); + } + break; + default: + return EINVAL; } - self = pthread_self(); int oldstate = _pthread_update_cancel_state(self, _PTHREAD_CANCEL_STATE_MASK, state); if (oldstateptr) { *oldstateptr = oldstate & _PTHREAD_CANCEL_STATE_MASK; } if (!conforming) { - _pthread_testcancel(self, 0); /* See if we need to 'die' now... */ + /* See if we need to 'die' now... */ + _pthread_testcancel(PTHREAD_CONFORM_DARWIN_LEGACY); } return 0; } @@ -292,7 +281,8 @@ pthread_setcanceltype(int type, int *oldtype) *oldtype = oldstate & _PTHREAD_CANCEL_TYPE_MASK; } #if !__DARWIN_UNIX03 - _pthread_testcancel(self, 0); /* See if we need to 'die' now... */ + /* See if we need to 'die' now... */ + _pthread_testcancel(PTHREAD_CONFORM_DARWIN_LEGACY); #endif /* __DARWIN_UNIX03 */ return (0); } @@ -315,76 +305,190 @@ pthread_sigmask(int how, const sigset_t * set, sigset_t * oset) #ifndef BUILDING_VARIANT /* [ */ -static void -__posix_join_cleanup(void *arg) +typedef struct pthread_join_context_s { + pthread_t waiter; + void **value_ptr; + mach_port_t kport; + semaphore_t custom_stack_sema; + bool detached; +} pthread_join_context_s, *pthread_join_context_t; + +static inline void * +_pthread_get_exit_value(pthread_t thread) +{ + if (__unix_conforming && _pthread_is_canceled(thread)) { + return PTHREAD_CANCELED; + } + return thread->tl_exit_value; +} + +// called with _pthread_list_lock held +PTHREAD_NOEXPORT +semaphore_t +_pthread_joiner_prepost_wake(pthread_t thread) +{ + pthread_join_context_t ctx = thread->tl_join_ctx; + semaphore_t sema = MACH_PORT_NULL; + + if (thread->tl_joinable) { + sema = ctx->custom_stack_sema; + thread->tl_joinable = false; + } else { + ctx->detached = true; + thread->tl_join_ctx = NULL; + } + if (ctx->value_ptr) *ctx->value_ptr = _pthread_get_exit_value(thread); + return sema; +} + +static inline bool +_pthread_joiner_abort_wait(pthread_t thread, pthread_join_context_t ctx) +{ + bool aborted = false; + + _PTHREAD_LOCK(_pthread_list_lock); + if (!ctx->detached && thread->tl_exit_gate != MACH_PORT_DEAD) { + /* + * _pthread_joiner_prepost_wake() didn't happen + * allow another thread to join + */ + PTHREAD_DEBUG_ASSERT(thread->tl_join_ctx == ctx); + thread->tl_join_ctx = NULL; + thread->tl_exit_gate = MACH_PORT_NULL; + aborted = true; + } + _PTHREAD_UNLOCK(_pthread_list_lock); + return aborted; +} + +static int +_pthread_joiner_wait(pthread_t thread, pthread_join_context_t ctx, int conforming) { - pthread_t thread = (pthread_t)arg; + uint32_t *exit_gate = &thread->tl_exit_gate; + int ulock_op = UL_UNFAIR_LOCK | ULF_NO_ERRNO; + + if (conforming == PTHREAD_CONFORM_UNIX03_CANCELABLE) { + ulock_op |= ULF_WAIT_CANCEL_POINT; + } + + for (;;) { + uint32_t cur = os_atomic_load(exit_gate, acquire); + if (cur == MACH_PORT_DEAD) { + break; + } + if (os_unlikely(cur != ctx->kport)) { + PTHREAD_CLIENT_CRASH(cur, "pthread_join() state corruption"); + } + int ret = __ulock_wait(ulock_op, exit_gate, ctx->kport, 0); + switch (-ret) { + case 0: + case EFAULT: + break; + case EINTR: + /* + * POSIX says: + * + * As specified, either the pthread_join() call is canceled, or it + * succeeds, but not both. The difference is obvious to the + * application, since either a cancellation handler is run or + * pthread_join() returns. + * + * When __ulock_wait() returns EINTR, we check if we have been + * canceled, and if we have, we try to abort the wait. + * + * If we can't, it means the other thread finished the join while we + * were being canceled and commited the waiter to return from + * pthread_join(). Returning from the join then takes precedence + * over the cancelation which will be acted upon at the next + * cancelation point. + */ + if (conforming == PTHREAD_CONFORM_UNIX03_CANCELABLE && + _pthread_is_canceled(ctx->waiter)) { + if (_pthread_joiner_abort_wait(thread, ctx)) { + ctx->waiter->canceled = true; + pthread_exit(PTHREAD_CANCELED); + } + } + break; + } + } + + bool cleanup = false; + + _PTHREAD_LOCK(_pthread_list_lock); + // If pthread_detach() was called, we can't safely dereference the thread, + // else, decide who gets to deallocate the thread (see _pthread_terminate). + if (!ctx->detached) { + PTHREAD_DEBUG_ASSERT(thread->tl_join_ctx == ctx); + thread->tl_join_ctx = NULL; + cleanup = thread->tl_joiner_cleans_up; + } + _PTHREAD_UNLOCK(_pthread_list_lock); - _PTHREAD_LOCK(thread->lock); - /* leave another thread to join */ - thread->joiner = (struct _pthread *)NULL; - _PTHREAD_UNLOCK(thread->lock); + if (cleanup) { + _pthread_deallocate(thread, false); + } + return 0; } PTHREAD_NOEXPORT PTHREAD_NOINLINE int -_pthread_join(pthread_t thread, void **value_ptr, int conforming, - int (*_semwait_signal)(int, int, int, int, __int64_t, __int32_t)) +_pthread_join(pthread_t thread, void **value_ptr, int conforming) { - int res = 0; pthread_t self = pthread_self(); - kern_return_t kern_res; - semaphore_t joinsem, death = (semaphore_t)os_get_cached_semaphore(); + pthread_join_context_s ctx = { + .waiter = self, + .value_ptr = value_ptr, + .kport = MACH_PORT_NULL, + .custom_stack_sema = MACH_PORT_NULL, + }; + int res = 0; + kern_return_t kr; - if (!_pthread_is_valid(thread, PTHREAD_IS_VALID_LOCK_THREAD, NULL)) { - res = ESRCH; - goto out; + if (!_pthread_validate_thread_and_list_lock(thread)) { + return ESRCH; } - if (thread->sig != _PTHREAD_SIG) { - res = ESRCH; - } else if ((thread->detached & PTHREAD_CREATE_DETACHED) || - !(thread->detached & PTHREAD_CREATE_JOINABLE) || - (thread->joiner != NULL)) { + if (!thread->tl_joinable || (thread->tl_join_ctx != NULL)) { res = EINVAL; - } else if (thread == self || (self != NULL && self->joiner == thread)) { + } else if (thread == self || + (self->tl_join_ctx && self->tl_join_ctx->waiter == thread)) { res = EDEADLK; + } else if (thread->tl_exit_gate == MACH_PORT_DEAD) { + TAILQ_REMOVE(&__pthread_head, thread, tl_plist); + PTHREAD_DEBUG_ASSERT(thread->tl_joiner_cleans_up); + thread->tl_joinable = false; + if (value_ptr) *value_ptr = _pthread_get_exit_value(thread); + } else { + ctx.kport = _pthread_kernel_thread(thread); + thread->tl_exit_gate = ctx.kport; + thread->tl_join_ctx = &ctx; + if (thread->tl_has_custom_stack) { + ctx.custom_stack_sema = (semaphore_t)os_get_cached_semaphore(); + } } - if (res != 0) { - _PTHREAD_UNLOCK(thread->lock); - goto out; - } + _PTHREAD_UNLOCK(_pthread_list_lock); - joinsem = thread->joiner_notify; - if (joinsem == SEMAPHORE_NULL) { - thread->joiner_notify = joinsem = death; - death = MACH_PORT_NULL; + if (res == 0) { + if (ctx.kport == MACH_PORT_NULL) { + _pthread_deallocate(thread, false); + } else { + res = _pthread_joiner_wait(thread, &ctx, conforming); + } } - thread->joiner = self; - _PTHREAD_UNLOCK(thread->lock); - - if (conforming) { - /* Wait for it to signal... */ - pthread_cleanup_push(__posix_join_cleanup, (void *)thread); - do { - res = _semwait_signal(joinsem, 0, 0, 0, 0, 0); - } while ((res < 0) && (errno == EINTR)); - pthread_cleanup_pop(0); - } else { - /* Wait for it to signal... */ - kern_return_t (*_semaphore_wait)(semaphore_t) = - (void*)_semwait_signal; + if (res == 0 && ctx.custom_stack_sema && !ctx.detached) { + // threads with a custom stack need to make sure _pthread_terminate + // returned before the joiner is unblocked, the joiner may quickly + // deallocate the stack with rather dire consequences. + // + // When we reach this point we know the pthread_join has to succeed + // so this can't be a cancelation point. do { - kern_res = _semaphore_wait(joinsem); - } while (kern_res != KERN_SUCCESS); + kr = __semwait_signal_nocancel(ctx.custom_stack_sema, 0, 0, 0, 0, 0); + } while (kr != KERN_SUCCESS); } - - os_put_cached_semaphore((os_semaphore_t)joinsem); - res = _pthread_join_cleanup(thread, value_ptr, conforming); - -out: - if (death) { - os_put_cached_semaphore(death); + if (ctx.custom_stack_sema) { + os_put_cached_semaphore(ctx.custom_stack_sema); } return res; } @@ -398,82 +502,45 @@ out: int pthread_join(pthread_t thread, void **value_ptr) { -#if __DARWIN_UNIX03 - if (__unix_conforming == 0) - __unix_conforming = 1; - -#ifdef VARIANT_CANCELABLE - _pthread_testcancel(pthread_self(), 1); -#endif /* VARIANT_CANCELABLE */ - return _pthread_join(thread, value_ptr, 1, __semwait_signal); -#else - return _pthread_join(thread, value_ptr, 0, (void*)semaphore_wait); -#endif /* __DARWIN_UNIX03 */ - + int conforming = _pthread_conformance(); + if (conforming == PTHREAD_CONFORM_UNIX03_CANCELABLE) { + _pthread_testcancel(conforming); + } + return _pthread_join(thread, value_ptr, conforming); } int -pthread_cond_wait(pthread_cond_t *cond, - pthread_mutex_t *mutex) +pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) { - int conforming; -#if __DARWIN_UNIX03 - - if (__unix_conforming == 0) - __unix_conforming = 1; - -#ifdef VARIANT_CANCELABLE - conforming = 1; -#else /* !VARIANT_CANCELABLE */ - conforming = -1; -#endif /* VARIANT_CANCELABLE */ -#else /* __DARWIN_UNIX03 */ - conforming = 0; -#endif /* __DARWIN_UNIX03 */ - return (_pthread_cond_wait(cond, mutex, (struct timespec *)NULL, 0, conforming)); + return _pthread_cond_wait(cond, mutex, NULL, 0, _pthread_conformance()); } int -pthread_cond_timedwait(pthread_cond_t *cond, - pthread_mutex_t *mutex, - const struct timespec *abstime) +pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, + const struct timespec *abstime) { - int conforming; -#if __DARWIN_UNIX03 - if (__unix_conforming == 0) - __unix_conforming = 1; - -#ifdef VARIANT_CANCELABLE - conforming = 1; -#else /* !VARIANT_CANCELABLE */ - conforming = -1; -#endif /* VARIANT_CANCELABLE */ -#else /* __DARWIN_UNIX03 */ - conforming = 0; -#endif /* __DARWIN_UNIX03 */ - - return (_pthread_cond_wait(cond, mutex, abstime, 0, conforming)); + return _pthread_cond_wait(cond, mutex, abstime, 0, _pthread_conformance()); } int sigwait(const sigset_t * set, int * sig) { #if __DARWIN_UNIX03 - int err = 0; + int err = 0, conformance = _pthread_conformance(); if (__unix_conforming == 0) __unix_conforming = 1; -#ifdef VARIANT_CANCELABLE - _pthread_testcancel(pthread_self(), 1); -#endif /* VARIANT_CANCELABLE */ + if (conformance == PTHREAD_CONFORM_UNIX03_CANCELABLE) { + _pthread_testcancel(conformance); + } if (__sigwait(set, sig) == -1) { err = errno; -#ifdef VARIANT_CANCELABLE - _pthread_testcancel(pthread_self(), 1); -#endif /* VARIANT_CANCELABLE */ + if (conformance == PTHREAD_CONFORM_UNIX03_CANCELABLE) { + _pthread_testcancel(conformance); + } /* * EINTR that isn't a result of pthread_cancel()