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