xref: /freebsd/lib/libthr/thread/thr_cancel.c (revision 2357939bc239bd5334a169b62313806178dd8f30)
1 /*
2  * David Leonard <d@openbsd.org>, 1999. Public domain.
3  * $FreeBSD$
4  */
5 #include <sys/errno.h>
6 #include <pthread.h>
7 #include <stdlib.h>
8 #include "thr_private.h"
9 
10 /*
11  * Static prototypes
12  */
13 static void	testcancel(void);
14 
15 __weak_reference(_pthread_cancel, pthread_cancel);
16 __weak_reference(_pthread_setcancelstate, pthread_setcancelstate);
17 __weak_reference(_pthread_setcanceltype, pthread_setcanceltype);
18 __weak_reference(_pthread_testcancel, pthread_testcancel);
19 
20 int
21 _pthread_cancel(pthread_t pthread)
22 {
23 	int ret;
24 	pthread_t joined;
25 
26 	/*
27 	 * When canceling a thread that has joined another thread, this
28 	 * routine breaks the normal lock order of locking first the
29 	 * joined and then the joiner. Therefore, it is necessary that
30 	 * if it can't obtain the second lock, that it release the first
31 	 * one and restart from the top.
32 	 */
33 retry:
34 	if ((ret = _find_thread(pthread)) != 0)
35 		/* The thread is not on the list of active threads */
36 		goto out;
37 
38 	_thread_critical_enter(pthread);
39 
40 	if (pthread->state == PS_DEAD || pthread->state == PS_DEADLOCK
41 	    || (pthread->flags & PTHREAD_EXITING) != 0) {
42 		/*
43 		 * The thread is in the process of (or has already) exited
44 		 * or is deadlocked.
45 		 */
46 		_thread_critical_exit(pthread);
47 		ret = 0;
48 		goto out;
49 	}
50 
51 	/*
52 	 * The thread is on the active thread list and is not in the process
53 	 * of exiting.
54 	 */
55 
56 	if (((pthread->cancelflags & PTHREAD_CANCEL_DISABLE) != 0) ||
57 	    (((pthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) == 0) &&
58 	    ((pthread->cancelflags & PTHREAD_AT_CANCEL_POINT) == 0)))
59 		/* Just mark it for cancellation: */
60 		pthread->cancelflags |= PTHREAD_CANCELLING;
61 	else {
62 		/*
63 		 * Check if we need to kick it back into the
64 		 * run queue:
65 		 */
66 		switch (pthread->state) {
67 		case PS_RUNNING:
68 			/* No need to resume: */
69 			pthread->cancelflags |= PTHREAD_CANCELLING;
70 			break;
71 
72 		case PS_SLEEP_WAIT:
73 		case PS_WAIT_WAIT:
74 			pthread->cancelflags |= PTHREAD_CANCELLING;
75 			PTHREAD_NEW_STATE(pthread, PS_RUNNING);
76 			break;
77 
78 		case PS_JOIN:
79 			/*
80 			 * Disconnect the thread from the joinee:
81 			 */
82 			if ((joined = pthread->join_status.thread) != NULL) {
83 				UMTX_TRYLOCK(&joined->lock, ret);
84 				if (ret == EBUSY) {
85 					_thread_critical_exit(pthread);
86 					goto retry;
87 				}
88 				pthread->join_status.thread->joiner = NULL;
89 				UMTX_UNLOCK(&joined->lock);
90 				joined = pthread->join_status.thread = NULL;
91 			}
92 			pthread->cancelflags |= PTHREAD_CANCELLING;
93 			PTHREAD_NEW_STATE(pthread, PS_RUNNING);
94 			break;
95 
96 		case PS_BARRIER_WAIT:
97 		case PS_MUTEX_WAIT:
98 		case PS_COND_WAIT:
99 			/*
100 			 * Threads in these states may be in queues.
101 			 * In order to preserve queue integrity, the
102 			 * cancelled thread must remove itself from the
103 			 * queue.  When the thread resumes, it will
104 			 * remove itself from the queue and call the
105 			 * cancellation routine.
106 			 */
107 			pthread->cancelflags |= PTHREAD_CANCELLING;
108 			PTHREAD_NEW_STATE(pthread, PS_RUNNING);
109 			break;
110 
111 		case PS_DEAD:
112 		case PS_DEADLOCK:
113 		case PS_STATE_MAX:
114 			/* Ignore - only here to silence -Wall: */
115 			break;
116 		}
117 	}
118 
119 	/* Unprotect the scheduling queues: */
120 	_thread_critical_exit(pthread);
121 
122 	ret = 0;
123 out:
124 	return (ret);
125 }
126 
127 int
128 _pthread_setcancelstate(int state, int *oldstate)
129 {
130 	int ostate, ret;
131 
132 	ret = 0;
133 
134 	_thread_critical_enter(curthread);
135 
136 	ostate = curthread->cancelflags & PTHREAD_CANCEL_DISABLE;
137 
138 	switch (state) {
139 	case PTHREAD_CANCEL_ENABLE:
140 		if (oldstate != NULL)
141 			*oldstate = ostate;
142 		curthread->cancelflags &= ~PTHREAD_CANCEL_DISABLE;
143 		if ((curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) == 0)
144 			break;
145 		testcancel();
146 		break;
147 	case PTHREAD_CANCEL_DISABLE:
148 		if (oldstate != NULL)
149 			*oldstate = ostate;
150 		curthread->cancelflags |= PTHREAD_CANCEL_DISABLE;
151 		break;
152 	default:
153 		ret = EINVAL;
154 	}
155 
156 	_thread_critical_exit(curthread);
157 	return (ret);
158 }
159 
160 int
161 _pthread_setcanceltype(int type, int *oldtype)
162 {
163 	int otype;
164 
165 	_thread_critical_enter(curthread);
166 	otype = curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS;
167 	switch (type) {
168 	case PTHREAD_CANCEL_ASYNCHRONOUS:
169 		if (oldtype != NULL)
170 			*oldtype = otype;
171 		curthread->cancelflags |= PTHREAD_CANCEL_ASYNCHRONOUS;
172 		testcancel();
173 		break;
174 	case PTHREAD_CANCEL_DEFERRED:
175 		if (oldtype != NULL)
176 			*oldtype = otype;
177 		curthread->cancelflags &= ~PTHREAD_CANCEL_ASYNCHRONOUS;
178 		break;
179 	default:
180 		return (EINVAL);
181 	}
182 
183 	_thread_critical_exit(curthread);
184 	return (0);
185 }
186 
187 void
188 _pthread_testcancel(void)
189 {
190 	_thread_critical_enter(curthread);
191 	testcancel();
192 	_thread_critical_exit(curthread);
193 }
194 
195 static void
196 testcancel()
197 {
198 	/*
199 	 * This pthread should already be locked by the caller.
200 	 */
201 
202 	if (((curthread->cancelflags & PTHREAD_CANCEL_DISABLE) == 0) &&
203 	    ((curthread->cancelflags & PTHREAD_CANCELLING) != 0) &&
204 	    ((curthread->flags & PTHREAD_EXITING) == 0)) {
205 		/*
206 		 * It is possible for this thread to be swapped out
207 		 * while performing cancellation; do not allow it
208 		 * to be cancelled again.
209 		 */
210 		curthread->cancelflags &= ~PTHREAD_CANCELLING;
211 		_thread_critical_exit(curthread);
212 		_thread_exit_cleanup();
213 		pthread_exit(PTHREAD_CANCELED);
214 		PANIC("cancel");
215 	}
216 }
217 
218 void
219 _thread_enter_cancellation_point(void)
220 {
221 	_thread_critical_enter(curthread);
222 	testcancel();
223 	curthread->cancelflags |= PTHREAD_AT_CANCEL_POINT;
224 	_thread_critical_exit(curthread);
225 }
226 
227 void
228 _thread_leave_cancellation_point(void)
229 {
230 	_thread_critical_enter(curthread);
231 	curthread->cancelflags &= ~PTHREAD_AT_CANCEL_POINT;
232 	testcancel();
233 	_thread_critical_exit(curthread);
234 
235 }
236