Files
musl/src/thread/pthread_cancel.c

97 lines
2.5 KiB
C
Raw Normal View History

#include <string.h>
#include "pthread_impl.h"
#include "syscall.h"
#include "libc.h"
long __cancel()
{
pthread_t self = __pthread_self();
if (self->canceldisable == PTHREAD_CANCEL_ENABLE || self->cancelasync)
pthread_exit(PTHREAD_CANCELED);
self->canceldisable = PTHREAD_CANCEL_DISABLE;
return -ECANCELED;
}
/* If __syscall_cp_asm has adjusted the stack pointer, it must provide a
* definition of __cp_cancel to undo those adjustments and call __cancel.
* Otherwise, __cancel provides a definition for __cp_cancel. */
weak_alias(__cancel, __cp_cancel);
long __syscall_cp_asm(volatile void *, syscall_arg_t,
syscall_arg_t, syscall_arg_t, syscall_arg_t,
syscall_arg_t, syscall_arg_t, syscall_arg_t);
long __syscall_cp_c(syscall_arg_t nr,
syscall_arg_t u, syscall_arg_t v, syscall_arg_t w,
syscall_arg_t x, syscall_arg_t y, syscall_arg_t z)
{
pthread_t self;
long r;
int st;
if ((st=(self=__pthread_self())->canceldisable)
&& (st==PTHREAD_CANCEL_DISABLE || nr==SYS_close))
return __syscall(nr, u, v, w, x, y, z);
r = __syscall_cp_asm(&self->cancel, nr, u, v, w, x, y, z);
if (r==-EINTR && nr!=SYS_close && self->cancel &&
self->canceldisable != PTHREAD_CANCEL_DISABLE)
r = __cancel();
return r;
}
static void _sigaddset(sigset_t *set, int sig)
{
unsigned s = sig-1;
set->__bits[s/8/sizeof *set->__bits] |= 1UL<<(s&8*sizeof *set->__bits-1);
}
static void cancel_handler(int sig, siginfo_t *si, void *ctx)
{
pthread_t self = __pthread_self();
ucontext_t *uc = ctx;
const char *ip = ((char **)&uc->uc_mcontext)[CANCEL_REG_IP];
extern const char __cp_begin[1], __cp_end[1];
fix missing memory barrier in cancellation signal handler in practice this was probably a non-issue, because the necessary barrier almost certainly exists in kernel space -- implementing signal delivery without such a barrier seems impossible -- but for the sake of correctness, it should be done here too. in principle, without a barrier, it is possible that the thread to be cancelled does not see the store of its cancellation flag performed by another thread. this affects both the case where the signal arrives before entering the critical program counter range from __cp_begin to __cp_end (in which case both the signal handler and the inline check fail to see the value which was already stored) and the case where the signal arrives during the critical range (in which case the signal handler should be responsible for cancellation, but when it does not see the cancellation flag, it assumes the signal is spurious and refuses to act on it). in the fix, the barrier is placed only in the signal handler, not in the inline check at the beginning of the critical program counter range. if the signal handler runs before the critical range is entered, it will of course take no action, but its barrier will ensure that the inline check subsequently sees the store. if on the other hand the inline check runs first, it may miss seeing the store, but the subsequent signal handler in the critical range will act upon the cancellation request. this strategy avoids adding a memory barrier in the common, non-cancellation code path.
2015-02-03 00:16:55 -05:00
a_barrier();
if (!self->cancel || self->canceldisable == PTHREAD_CANCEL_DISABLE) return;
_sigaddset(&uc->uc_sigmask, SIGCANCEL);
if (self->cancelasync || ip >= __cp_begin && ip < __cp_end) {
((char **)&uc->uc_mcontext)[CANCEL_REG_IP] = (char *)__cp_cancel;
return;
}
eliminate use of cached pid from thread structure the main motivation for this change is to remove the assumption that the tid of the main thread is also the pid of the process. (the value returned by the set_tid_address syscall was used to fill both fields despite it semantically being the tid.) this is historically and presently true on linux and unlikely to change, but it conceivably could be false on other systems that otherwise reproduce the linux syscall api/abi. only a few parts of the code were actually still using the cached pid. in a couple places (aio and synccall) it was a minor optimization to avoid a syscall. caching could be reintroduced, but lazily as part of the public getpid function rather than at program startup, if it's deemed important for performance later. in other places (cancellation and pthread_kill) the pid was completely unnecessary; the tkill syscall can be used instead of tgkill. this is actually a rather subtle issue, since tgkill is supposedly a solution to race conditions that can affect use of tkill. however, as documented in the commit message for commit 7779dbd2663269b465951189b4f43e70839bc073, tgkill does not actually solve this race; it just limits it to happening within one process rather than between processes. we use a lock that avoids the race in pthread_kill, and the use in the cancellation signal handler is self-targeted and thus not subject to tid reuse races, so both are safe regardless of which syscall (tgkill or tkill) is used.
2014-07-05 23:29:55 -04:00
__syscall(SYS_tkill, self->tid, SIGCANCEL);
}
void __testcancel()
{
pthread_t self = __pthread_self();
if (self->cancel && !self->canceldisable)
__cancel();
}
static void init_cancellation()
{
struct sigaction sa = {
.sa_flags = SA_SIGINFO | SA_RESTART,
.sa_sigaction = cancel_handler
};
memset(&sa.sa_mask, -1, _NSIG/8);
__libc_sigaction(SIGCANCEL, &sa, 0);
}
int pthread_cancel(pthread_t t)
{
static int init;
if (!init) {
init_cancellation();
init = 1;
}
a_store(&t->cancel, 1);
return pthread_kill(t, SIGCANCEL);
}