0a3fa43c7e
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
203 lines
4.6 KiB
C
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);
|
|
}
|
|
}
|