freebsd-dev/lib/libc_r/uthread/uthread_cancel.c
Daniel Eischen fc8f3f5bfe Fix problems with cancellation while in critical regions.
o Cancellation flags were not getting properly set/cleared.
  o Loops waiting for internal locks were not being exited
    correctly by a cancelled thread.
  o Minor spelling (cancelation -> cancellation) and formatting
    corrections (missing tab).

Found by:	tg
Reviewed by:	jasone
1999-12-17 00:57:54 +00:00

189 lines
4.2 KiB
C

/*
* David Leonard <d@openbsd.org>, 1999. Public domain.
* $FreeBSD$
*/
#include <sys/errno.h>
#include <pthread.h>
#include "pthread_private.h"
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
* exit after removing itself from the queue.
*/
pthread->interrupted = 1;
pthread->cancelflags |= PTHREAD_CANCEL_NEEDED;
PTHREAD_NEW_STATE(pthread,PS_RUNNING);
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();
}