xref: /freebsd/lib/libthr/thread/thr_cancel.c (revision f51e58036ebe3a3e75527325e659d7ba02b129ed)
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 				if (_spintrylock(&joined->lock) == EBUSY) {
84 					_thread_critical_exit(pthread);
85 					goto retry;
86 				}
87 				pthread->join_status.thread->joiner = NULL;
88 				_spinunlock(&joined->lock);
89 				joined = pthread->join_status.thread = NULL;
90 			}
91 			pthread->cancelflags |= PTHREAD_CANCELLING;
92 			PTHREAD_NEW_STATE(pthread, PS_RUNNING);
93 			break;
94 
95 		case PS_MUTEX_WAIT:
96 		case PS_COND_WAIT:
97 			/*
98 			 * Threads in these states may be in queues.
99 			 * In order to preserve queue integrity, the
100 			 * cancelled thread must remove itself from the
101 			 * queue.  When the thread resumes, it will
102 			 * remove itself from the queue and call the
103 			 * cancellation routine.
104 			 */
105 			pthread->cancelflags |= PTHREAD_CANCELLING;
106 			PTHREAD_NEW_STATE(pthread, PS_RUNNING);
107 			break;
108 
109 		case PS_DEAD:
110 		case PS_DEADLOCK:
111 		case PS_STATE_MAX:
112 			/* Ignore - only here to silence -Wall: */
113 			break;
114 		}
115 	}
116 
117 	/* Unprotect the scheduling queues: */
118 	_thread_critical_exit(pthread);
119 
120 	ret = 0;
121 out:
122 	return (ret);
123 }
124 
125 int
126 _pthread_setcancelstate(int state, int *oldstate)
127 {
128 	int ostate, ret;
129 
130 	ret = 0;
131 
132 	_thread_critical_enter(curthread);
133 
134 	ostate = curthread->cancelflags & PTHREAD_CANCEL_DISABLE;
135 
136 	switch (state) {
137 	case PTHREAD_CANCEL_ENABLE:
138 		if (oldstate != NULL)
139 			*oldstate = ostate;
140 		curthread->cancelflags &= ~PTHREAD_CANCEL_DISABLE;
141 		if ((curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) == 0)
142 			break;
143 		testcancel();
144 		break;
145 	case PTHREAD_CANCEL_DISABLE:
146 		if (oldstate != NULL)
147 			*oldstate = ostate;
148 		curthread->cancelflags |= PTHREAD_CANCEL_DISABLE;
149 		break;
150 	default:
151 		ret = EINVAL;
152 	}
153 
154 	_thread_critical_exit(curthread);
155 	return (ret);
156 }
157 
158 int
159 _pthread_setcanceltype(int type, int *oldtype)
160 {
161 	int otype;
162 
163 	_thread_critical_enter(curthread);
164 	otype = curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS;
165 	switch (type) {
166 	case PTHREAD_CANCEL_ASYNCHRONOUS:
167 		if (oldtype != NULL)
168 			*oldtype = otype;
169 		curthread->cancelflags |= PTHREAD_CANCEL_ASYNCHRONOUS;
170 		testcancel();
171 		break;
172 	case PTHREAD_CANCEL_DEFERRED:
173 		if (oldtype != NULL)
174 			*oldtype = otype;
175 		curthread->cancelflags &= ~PTHREAD_CANCEL_ASYNCHRONOUS;
176 		break;
177 	default:
178 		return (EINVAL);
179 	}
180 
181 	_thread_critical_exit(curthread);
182 	return (0);
183 }
184 
185 void
186 _pthread_testcancel(void)
187 {
188 	_thread_critical_enter(curthread);
189 	testcancel();
190 	_thread_critical_exit(curthread);
191 }
192 
193 static void
194 testcancel()
195 {
196 	/*
197 	 * This pthread should already be locked by the caller.
198 	 */
199 
200 	if (((curthread->cancelflags & PTHREAD_CANCEL_DISABLE) == 0) &&
201 	    ((curthread->cancelflags & PTHREAD_CANCELLING) != 0) &&
202 	    ((curthread->flags & PTHREAD_EXITING) == 0)) {
203 		/*
204 		 * It is possible for this thread to be swapped out
205 		 * while performing cancellation; do not allow it
206 		 * to be cancelled again.
207 		 */
208 		curthread->cancelflags &= ~PTHREAD_CANCELLING;
209 		_thread_critical_exit(curthread);
210 		_thread_exit_cleanup();
211 		pthread_exit(PTHREAD_CANCELED);
212 		PANIC("cancel");
213 	}
214 }
215 
216 void
217 _thread_enter_cancellation_point(void)
218 {
219 	_thread_critical_enter(curthread);
220 	testcancel();
221 	curthread->cancelflags |= PTHREAD_AT_CANCEL_POINT;
222 	_thread_critical_exit(curthread);
223 }
224 
225 void
226 _thread_leave_cancellation_point(void)
227 {
228 	_thread_critical_enter(curthread);
229 	curthread->cancelflags &= ~PTHREAD_AT_CANCEL_POINT;
230 	testcancel();
231 	_thread_critical_exit(curthread);
232 
233 }
234