freebsd-dev/lib/libpthread/thread/thr_cancel.c
Jason Evans 0a3fa43c7e Implement continuations to correctly handle [sig|_]longjmp() inside of a
signal handler.  Explicitly check for jumps to anywhere other than the
current stack, since such jumps are undefined according to POSIX.

While we're at it, convert thread cancellation to use continuations, since
it's cleaner than the original cancellation code.

Avoid delivering a signal to a thread twice.  This was a pre-existing bug,
but was likely unexposed until these other changes were made.

Defer signals generated by pthread_kill() so that they can be delivered on
the appropriate stack.  deischen claims that this is unnecessary, which is
likely true, but without this change, pthread_kill() can cause undefined
priority queue states and/or PANICs in [sig|_]longjmp(), so I'm leaving
this in for now.  To compile this code out and exercise the bug, define
the _NO_UNDISPATCH cpp macro.  Defining _PTHREADS_INVARIANTS as well will
cause earlier crashes.

PR:			kern/14685
Collaboration with:	deischen
2000-01-19 07:04:50 +00:00

203 lines
4.6 KiB
C

/*
* David Leonard <d@openbsd.org>, 1999. Public domain.
* $FreeBSD$
*/
#include <sys/errno.h>
#include <pthread.h>
#include "pthread_private.h"
static void finish_cancellation(void *arg);
int
pthread_cancel(pthread_t pthread)
{
int ret;
if ((ret = _find_thread(pthread)) != 0) {
/* NOTHING */
} else if (pthread->state == PS_DEAD || pthread->state == PS_DEADLOCK) {
ret = 0;
} else {
/* Protect the scheduling queues: */
_thread_kern_sig_defer();
if (((pthread->cancelflags & PTHREAD_CANCEL_DISABLE) != 0) ||
(((pthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) == 0) &&
((pthread->cancelflags & PTHREAD_AT_CANCEL_POINT) == 0)))
/* Just mark it for cancellation: */
pthread->cancelflags |= PTHREAD_CANCELLING;
else {
/*
* Check if we need to kick it back into the
* run queue:
*/
switch (pthread->state) {
case PS_RUNNING:
/* No need to resume: */
pthread->cancelflags |= PTHREAD_CANCELLING;
break;
case PS_SPINBLOCK:
case PS_FDR_WAIT:
case PS_FDW_WAIT:
case PS_POLL_WAIT:
case PS_SELECT_WAIT:
/* Remove these threads from the work queue: */
if ((pthread->flags & PTHREAD_FLAGS_IN_WORKQ)
!= 0)
PTHREAD_WORKQ_REMOVE(pthread);
/* Fall through: */
case PS_SIGTHREAD:
case PS_SLEEP_WAIT:
case PS_WAIT_WAIT:
case PS_SIGSUSPEND:
case PS_SIGWAIT:
case PS_SUSPENDED:
/* Interrupt and resume: */
pthread->interrupted = 1;
pthread->cancelflags |= PTHREAD_CANCELLING;
PTHREAD_NEW_STATE(pthread,PS_RUNNING);
break;
case PS_MUTEX_WAIT:
case PS_COND_WAIT:
case PS_FDLR_WAIT:
case PS_FDLW_WAIT:
case PS_FILE_WAIT:
case PS_JOIN:
/*
* Threads in these states may be in queues.
* In order to preserve queue integrity, the
* cancelled thread must remove itself from the
* queue. Mark the thread as interrupted and
* needing cancellation, and set the state to
* running. When the thread resumes, it will
* remove itself from the queue and call the
* cancellation completion routine.
*/
pthread->interrupted = 1;
pthread->cancelflags |= PTHREAD_CANCEL_NEEDED;
PTHREAD_NEW_STATE(pthread,PS_RUNNING);
pthread->continuation = finish_cancellation;
break;
case PS_DEAD:
case PS_DEADLOCK:
case PS_STATE_MAX:
/* Ignore - only here to silence -Wall: */
break;
}
}
/* Unprotect the scheduling queues: */
_thread_kern_sig_undefer();
ret = 0;
}
return (ret);
}
int
pthread_setcancelstate(int state, int *oldstate)
{
int ostate;
int ret;
ostate = _thread_run->cancelflags & PTHREAD_CANCEL_DISABLE;
switch (state) {
case PTHREAD_CANCEL_ENABLE:
if (oldstate != NULL)
*oldstate = ostate;
_thread_run->cancelflags &= ~PTHREAD_CANCEL_DISABLE;
if ((_thread_run->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) != 0)
pthread_testcancel();
ret = 0;
break;
case PTHREAD_CANCEL_DISABLE:
if (oldstate != NULL)
*oldstate = ostate;
_thread_run->cancelflags |= PTHREAD_CANCEL_DISABLE;
ret = 0;
break;
default:
ret = EINVAL;
}
return (ret);
}
int
pthread_setcanceltype(int type, int *oldtype)
{
int otype;
int ret;
otype = _thread_run->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS;
switch (type) {
case PTHREAD_CANCEL_ASYNCHRONOUS:
if (oldtype != NULL)
*oldtype = otype;
_thread_run->cancelflags |= PTHREAD_CANCEL_ASYNCHRONOUS;
pthread_testcancel();
ret = 0;
break;
case PTHREAD_CANCEL_DEFERRED:
if (oldtype != NULL)
*oldtype = otype;
_thread_run->cancelflags &= ~PTHREAD_CANCEL_ASYNCHRONOUS;
ret = 0;
break;
default:
ret = EINVAL;
}
return (ret);
}
void
pthread_testcancel(void)
{
if (((_thread_run->cancelflags & PTHREAD_CANCEL_DISABLE) == 0) &&
((_thread_run->cancelflags & PTHREAD_CANCELLING) != 0)) {
/*
* It is possible for this thread to be swapped out
* while performing cancellation; do not allow it
* to be cancelled again.
*/
_thread_run->cancelflags &= ~PTHREAD_CANCELLING;
_thread_exit_cleanup();
pthread_exit(PTHREAD_CANCELED);
PANIC("cancel");
}
}
void
_thread_enter_cancellation_point(void)
{
/* Look for a cancellation before we block: */
pthread_testcancel();
_thread_run->cancelflags |= PTHREAD_AT_CANCEL_POINT;
}
void
_thread_leave_cancellation_point(void)
{
_thread_run->cancelflags &= ~PTHREAD_AT_CANCEL_POINT;
/* Look for a cancellation after we unblock: */
pthread_testcancel();
}
static void
finish_cancellation(void *arg)
{
_thread_run->continuation = NULL;
_thread_run->interrupted = 0;
if ((_thread_run->cancelflags & PTHREAD_CANCEL_NEEDED) != 0) {
_thread_run->cancelflags &= ~PTHREAD_CANCEL_NEEDED;
_thread_exit_cleanup();
pthread_exit(PTHREAD_CANCELED);
}
}