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