thr_cancel.c revision 118510
1/*
2 * David Leonard <d@openbsd.org>, 1999. Public domain.
3 * $FreeBSD: head/lib/libkse/thread/thr_cancel.c 118510 2003-08-05 22:46:00Z deischen $
4 */
5#include <sys/errno.h>
6#include <pthread.h>
7#include "thr_private.h"
8
9__weak_reference(_pthread_cancel, pthread_cancel);
10__weak_reference(_pthread_setcancelstate, pthread_setcancelstate);
11__weak_reference(_pthread_setcanceltype, pthread_setcanceltype);
12__weak_reference(_pthread_testcancel, pthread_testcancel);
13
14static int	checkcancel(struct pthread *curthread);
15static void	testcancel(struct pthread *curthread);
16static void	finish_cancellation(void *arg);
17
18int
19_pthread_cancel(pthread_t pthread)
20{
21	struct pthread *curthread = _get_curthread();
22	struct pthread *joinee = NULL;
23	struct kse_mailbox *kmbx = NULL;
24	int ret;
25
26	if ((ret = _thr_ref_add(curthread, pthread, /*include dead*/0)) == 0) {
27		/*
28		 * Take the scheduling lock while we change the cancel flags.
29		 */
30		THR_SCHED_LOCK(curthread, pthread);
31		if (pthread->flags & THR_FLAGS_EXITING) {
32			THR_SCHED_UNLOCK(curthread, pthread);
33			_thr_ref_delete(curthread, pthread);
34			return (ESRCH);
35		}
36		if (((pthread->cancelflags & PTHREAD_CANCEL_DISABLE) != 0) ||
37		    (((pthread->cancelflags & THR_AT_CANCEL_POINT) == 0) &&
38		    ((pthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) == 0)))
39			/* Just mark it for cancellation: */
40			pthread->cancelflags |= THR_CANCELLING;
41		else {
42			/*
43			 * Check if we need to kick it back into the
44			 * run queue:
45			 */
46			switch (pthread->state) {
47			case PS_RUNNING:
48				/* No need to resume: */
49				pthread->cancelflags |= THR_CANCELLING;
50				break;
51
52			case PS_LOCKWAIT:
53				/*
54				 * These can't be removed from the queue.
55				 * Just mark it as cancelling and tell it
56				 * to yield once it leaves the critical
57				 * region.
58				 */
59				pthread->cancelflags |= THR_CANCELLING;
60				pthread->critical_yield = 1;
61				break;
62
63			case PS_SLEEP_WAIT:
64			case PS_SIGSUSPEND:
65			case PS_SIGWAIT:
66				/* Interrupt and resume: */
67				pthread->interrupted = 1;
68				pthread->cancelflags |= THR_CANCELLING;
69				kmbx = _thr_setrunnable_unlocked(pthread);
70				break;
71
72			case PS_JOIN:
73				/* Disconnect the thread from the joinee: */
74				joinee = pthread->join_status.thread;
75				pthread->join_status.thread = NULL;
76				pthread->cancelflags |= THR_CANCELLING;
77				kmbx = _thr_setrunnable_unlocked(pthread);
78				if ((joinee != NULL) &&
79				    (pthread->kseg == joinee->kseg)) {
80					/* Remove the joiner from the joinee. */
81					joinee->joiner = NULL;
82					joinee = NULL;
83				}
84				break;
85
86			case PS_SUSPENDED:
87			case PS_MUTEX_WAIT:
88			case PS_COND_WAIT:
89				/*
90				 * Threads in these states may be in queues.
91				 * In order to preserve queue integrity, the
92				 * cancelled thread must remove itself from the
93				 * queue.  Mark the thread as interrupted and
94				 * needing cancellation, and set the state to
95				 * running.  When the thread resumes, it will
96				 * remove itself from the queue and call the
97				 * cancellation completion routine.
98				 */
99				pthread->interrupted = 1;
100				pthread->cancelflags |= THR_CANCEL_NEEDED;
101				kmbx = _thr_setrunnable_unlocked(pthread);
102				pthread->continuation = finish_cancellation;
103				break;
104
105			case PS_DEAD:
106			case PS_DEADLOCK:
107			case PS_STATE_MAX:
108				/* Ignore - only here to silence -Wall: */
109				break;
110			}
111			if ((pthread->cancelflags & THR_AT_CANCEL_POINT) &&
112			    (pthread->blocked != 0 ||
113			     pthread->attr.flags & PTHREAD_SCOPE_SYSTEM))
114				kse_thr_interrupt(&pthread->tcb->tcb_tmbx,
115					KSE_INTR_INTERRUPT, 0);
116		}
117
118		/*
119		 * Release the thread's scheduling lock and remove the
120		 * reference:
121		 */
122		THR_SCHED_UNLOCK(curthread, pthread);
123		_thr_ref_delete(curthread, pthread);
124		if (kmbx != NULL)
125			kse_wakeup(kmbx);
126
127		if ((joinee != NULL) &&
128		    (_thr_ref_add(curthread, joinee, /* include dead */1) == 0)) {
129			/* Remove the joiner from the joinee. */
130			THR_SCHED_LOCK(curthread, joinee);
131			joinee->joiner = NULL;
132			THR_SCHED_UNLOCK(curthread, joinee);
133			_thr_ref_delete(curthread, joinee);
134		}
135	}
136	return (ret);
137}
138
139int
140_pthread_setcancelstate(int state, int *oldstate)
141{
142	struct pthread	*curthread = _get_curthread();
143	int ostate;
144	int ret;
145	int need_exit = 0;
146
147	/* Take the scheduling lock while fiddling with the thread's state: */
148	THR_SCHED_LOCK(curthread, curthread);
149
150	ostate = curthread->cancelflags & PTHREAD_CANCEL_DISABLE;
151
152	switch (state) {
153	case PTHREAD_CANCEL_ENABLE:
154		curthread->cancelflags &= ~PTHREAD_CANCEL_DISABLE;
155		if ((curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) != 0)
156			need_exit = checkcancel(curthread);
157		ret = 0;
158		break;
159	case PTHREAD_CANCEL_DISABLE:
160		curthread->cancelflags |= PTHREAD_CANCEL_DISABLE;
161		ret = 0;
162		break;
163	default:
164		ret = EINVAL;
165	}
166
167	THR_SCHED_UNLOCK(curthread, curthread);
168	if (need_exit != 0) {
169		_thr_exit_cleanup();
170		pthread_exit(PTHREAD_CANCELED);
171		PANIC("cancel");
172	}
173	if (ret == 0 && oldstate != NULL)
174		*oldstate = ostate;
175
176	return (ret);
177}
178
179int
180_pthread_setcanceltype(int type, int *oldtype)
181{
182	struct pthread	*curthread = _get_curthread();
183	int otype;
184	int ret;
185	int need_exit = 0;
186
187	/* Take the scheduling lock while fiddling with the state: */
188	THR_SCHED_LOCK(curthread, curthread);
189
190	otype = curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS;
191	switch (type) {
192	case PTHREAD_CANCEL_ASYNCHRONOUS:
193		curthread->cancelflags |= PTHREAD_CANCEL_ASYNCHRONOUS;
194		need_exit = checkcancel(curthread);
195		ret = 0;
196		break;
197	case PTHREAD_CANCEL_DEFERRED:
198		curthread->cancelflags &= ~PTHREAD_CANCEL_ASYNCHRONOUS;
199		ret = 0;
200		break;
201	default:
202		ret = EINVAL;
203	}
204
205	THR_SCHED_UNLOCK(curthread, curthread);
206	if (need_exit != 0) {
207		_thr_exit_cleanup();
208		pthread_exit(PTHREAD_CANCELED);
209		PANIC("cancel");
210	}
211	if (ret == 0 && oldtype != NULL)
212		*oldtype = otype;
213
214	return (ret);
215}
216
217static int
218checkcancel(struct pthread *curthread)
219{
220	if (((curthread->cancelflags & PTHREAD_CANCEL_DISABLE) == 0) &&
221	    ((curthread->cancelflags & THR_CANCELLING) != 0)) {
222		/*
223		 * It is possible for this thread to be swapped out
224		 * while performing cancellation; do not allow it
225		 * to be cancelled again.
226		 */
227		curthread->cancelflags &= ~THR_CANCELLING;
228		return (1);
229	}
230	else
231		return (0);
232}
233
234static void
235testcancel(struct pthread *curthread)
236{
237	/* Take the scheduling lock while fiddling with the state: */
238
239	if (checkcancel(curthread) != 0) {
240		/* Unlock before exiting: */
241		THR_SCHED_UNLOCK(curthread, curthread);
242
243		_thr_exit_cleanup();
244		pthread_exit(PTHREAD_CANCELED);
245		PANIC("cancel");
246	}
247}
248
249void
250_pthread_testcancel(void)
251{
252	struct pthread	*curthread = _get_curthread();
253
254	THR_SCHED_LOCK(curthread, curthread);
255	testcancel(curthread);
256	THR_SCHED_UNLOCK(curthread, curthread);
257}
258
259void
260_thr_enter_cancellation_point(struct pthread *thread)
261{
262	/* Look for a cancellation before we block: */
263	THR_SCHED_LOCK(thread, thread);
264	testcancel(thread);
265	thread->cancelflags |= THR_AT_CANCEL_POINT;
266	THR_SCHED_UNLOCK(thread, thread);
267}
268
269void
270_thr_leave_cancellation_point(struct pthread *thread)
271{
272	THR_SCHED_LOCK(thread, thread);
273	thread->cancelflags &= ~THR_AT_CANCEL_POINT;
274	/* Look for a cancellation after we unblock: */
275	testcancel(thread);
276	THR_SCHED_UNLOCK(thread, thread);
277}
278
279static void
280finish_cancellation(void *arg)
281{
282	struct pthread	*curthread = _get_curthread();
283
284	curthread->continuation = NULL;
285	curthread->interrupted = 0;
286
287	THR_SCHED_LOCK(curthread, curthread);
288	if ((curthread->cancelflags & THR_CANCEL_NEEDED) != 0) {
289		curthread->cancelflags &= ~THR_CANCEL_NEEDED;
290		THR_SCHED_UNLOCK(curthread, curthread);
291		_thr_exit_cleanup();
292		pthread_exit(PTHREAD_CANCELED);
293	}
294	THR_SCHED_UNLOCK(curthread, curthread);
295}
296