thr_cancel.c revision 87988
1/*
2 * David Leonard <d@openbsd.org>, 1999. Public domain.
3 * $FreeBSD: head/lib/libkse/thread/thr_cancel.c 87988 2001-12-15 15:52:24Z deischen $
4 */
5#include <sys/errno.h>
6#include <pthread.h>
7#include "pthread_private.h"
8
9static void	finish_cancellation(void *arg);
10
11__weak_reference(_pthread_cancel, pthread_cancel);
12__weak_reference(_pthread_setcancelstate, pthread_setcancelstate);
13__weak_reference(_pthread_setcanceltype, pthread_setcanceltype);
14__weak_reference(_pthread_testcancel, pthread_testcancel);
15
16int
17_pthread_cancel(pthread_t pthread)
18{
19	int ret;
20
21	if ((ret = _find_thread(pthread)) != 0) {
22		/* NOTHING */
23	} else if (pthread->state == PS_DEAD || pthread->state == PS_DEADLOCK) {
24		ret = 0;
25	} else {
26		/* Protect the scheduling queues: */
27		_thread_kern_sig_defer();
28
29		if (((pthread->cancelflags & PTHREAD_CANCEL_DISABLE) != 0) ||
30		    (((pthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) == 0) &&
31		    ((pthread->cancelflags & PTHREAD_AT_CANCEL_POINT) == 0)))
32			/* Just mark it for cancellation: */
33			pthread->cancelflags |= PTHREAD_CANCELLING;
34		else {
35			/*
36			 * Check if we need to kick it back into the
37			 * run queue:
38			 */
39			switch (pthread->state) {
40			case PS_RUNNING:
41				/* No need to resume: */
42				pthread->cancelflags |= PTHREAD_CANCELLING;
43				break;
44
45			case PS_SPINBLOCK:
46			case PS_FDR_WAIT:
47			case PS_FDW_WAIT:
48			case PS_POLL_WAIT:
49			case PS_SELECT_WAIT:
50				/* Remove these threads from the work queue: */
51				if ((pthread->flags & PTHREAD_FLAGS_IN_WORKQ)
52				    != 0)
53					PTHREAD_WORKQ_REMOVE(pthread);
54				/* Fall through: */
55			case PS_SIGTHREAD:
56			case PS_SLEEP_WAIT:
57			case PS_WAIT_WAIT:
58			case PS_SIGSUSPEND:
59			case PS_SIGWAIT:
60				/* Interrupt and resume: */
61				pthread->interrupted = 1;
62				pthread->cancelflags |= PTHREAD_CANCELLING;
63				PTHREAD_NEW_STATE(pthread,PS_RUNNING);
64				break;
65
66			case PS_JOIN:
67				/*
68				 * Disconnect the thread from the joinee and
69				 * detach:
70				 */
71				if (pthread->join_status.thread != NULL) {
72					pthread->join_status.thread->joiner
73					    = NULL;
74					pthread_detach((pthread_t)
75					    pthread->join_status.thread);
76				}
77				pthread->cancelflags |= PTHREAD_CANCELLING;
78				PTHREAD_NEW_STATE(pthread, PS_RUNNING);
79				break;
80
81			case PS_SUSPENDED:
82				if (pthread->suspended == SUSP_NO ||
83				    pthread->suspended == SUSP_YES ||
84				    pthread->suspended == SUSP_JOIN ||
85				    pthread->suspended == SUSP_NOWAIT) {
86					/*
87					 * This thread isn't in any scheduling
88					 * queues; just change it's state:
89					 */
90					pthread->cancelflags |=
91					    PTHREAD_CANCELLING;
92					PTHREAD_SET_STATE(pthread, PS_RUNNING);
93					break;
94				}
95				/* FALLTHROUGH */
96			case PS_MUTEX_WAIT:
97			case PS_COND_WAIT:
98			case PS_FDLR_WAIT:
99			case PS_FDLW_WAIT:
100			case PS_FILE_WAIT:
101				/*
102				 * Threads in these states may be in queues.
103				 * In order to preserve queue integrity, the
104				 * cancelled thread must remove itself from the
105				 * queue.  Mark the thread as interrupted and
106				 * needing cancellation, and set the state to
107				 * running.  When the thread resumes, it will
108				 * remove itself from the queue and call the
109				 * cancellation completion routine.
110				 */
111				pthread->interrupted = 1;
112				pthread->cancelflags |= PTHREAD_CANCEL_NEEDED;
113				PTHREAD_NEW_STATE(pthread,PS_RUNNING);
114				pthread->continuation = finish_cancellation;
115				break;
116
117			case PS_DEAD:
118			case PS_DEADLOCK:
119			case PS_STATE_MAX:
120				/* Ignore - only here to silence -Wall: */
121				break;
122			}
123		}
124
125		/* Unprotect the scheduling queues: */
126		_thread_kern_sig_undefer();
127
128		ret = 0;
129	}
130	return (ret);
131}
132
133int
134_pthread_setcancelstate(int state, int *oldstate)
135{
136	struct pthread	*curthread = _get_curthread();
137	int ostate;
138	int ret;
139
140	ostate = curthread->cancelflags & PTHREAD_CANCEL_DISABLE;
141
142	switch (state) {
143	case PTHREAD_CANCEL_ENABLE:
144		if (oldstate != NULL)
145			*oldstate = ostate;
146		curthread->cancelflags &= ~PTHREAD_CANCEL_DISABLE;
147		if ((curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) != 0)
148			pthread_testcancel();
149		ret = 0;
150		break;
151	case PTHREAD_CANCEL_DISABLE:
152		if (oldstate != NULL)
153			*oldstate = ostate;
154		curthread->cancelflags |= PTHREAD_CANCEL_DISABLE;
155		ret = 0;
156		break;
157	default:
158		ret = EINVAL;
159	}
160
161	return (ret);
162}
163
164int
165_pthread_setcanceltype(int type, int *oldtype)
166{
167	struct pthread	*curthread = _get_curthread();
168	int otype;
169	int ret;
170
171	otype = curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS;
172	switch (type) {
173	case PTHREAD_CANCEL_ASYNCHRONOUS:
174		if (oldtype != NULL)
175			*oldtype = otype;
176		curthread->cancelflags |= PTHREAD_CANCEL_ASYNCHRONOUS;
177		pthread_testcancel();
178		ret = 0;
179		break;
180	case PTHREAD_CANCEL_DEFERRED:
181		if (oldtype != NULL)
182			*oldtype = otype;
183		curthread->cancelflags &= ~PTHREAD_CANCEL_ASYNCHRONOUS;
184		ret = 0;
185		break;
186	default:
187		ret = EINVAL;
188	}
189
190	return (ret);
191}
192
193void
194_pthread_testcancel(void)
195{
196	struct pthread	*curthread = _get_curthread();
197
198	if (((curthread->cancelflags & PTHREAD_CANCEL_DISABLE) == 0) &&
199	    ((curthread->cancelflags & PTHREAD_CANCELLING) != 0)) {
200		/*
201		 * It is possible for this thread to be swapped out
202		 * while performing cancellation; do not allow it
203		 * to be cancelled again.
204		 */
205		curthread->cancelflags &= ~PTHREAD_CANCELLING;
206		_thread_exit_cleanup();
207		pthread_exit(PTHREAD_CANCELED);
208		PANIC("cancel");
209	}
210}
211
212void
213_thread_enter_cancellation_point(void)
214{
215	struct pthread	*curthread = _get_curthread();
216
217	/* Look for a cancellation before we block: */
218	pthread_testcancel();
219	curthread->cancelflags |= PTHREAD_AT_CANCEL_POINT;
220}
221
222void
223_thread_leave_cancellation_point(void)
224{
225	struct pthread	*curthread = _get_curthread();
226
227	curthread->cancelflags &= ~PTHREAD_AT_CANCEL_POINT;
228	/* Look for a cancellation after we unblock: */
229	pthread_testcancel();
230}
231
232static void
233finish_cancellation(void *arg)
234{
235	struct pthread	*curthread = _get_curthread();
236
237	curthread->continuation = NULL;
238	curthread->interrupted = 0;
239
240	if ((curthread->cancelflags & PTHREAD_CANCEL_NEEDED) != 0) {
241		curthread->cancelflags &= ~PTHREAD_CANCEL_NEEDED;
242		_thread_exit_cleanup();
243		pthread_exit(PTHREAD_CANCELED);
244	}
245}
246