thr_cancel.c revision 88015
1/*
2 * David Leonard <d@openbsd.org>, 1999. Public domain.
3 * $FreeBSD: head/lib/libkse/thread/thr_cancel.c 88015 2001-12-16 13:26:44Z deischen $
4 */
5#include <sys/errno.h>
6#include <pthread.h>
7#include "pthread_private.h"
8
9static void	finish_cancellation(void *arg);
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
16int
17_pthread_cancel(pthread_t pthread)
18{
19	int ret;
20
21	if ((ret = _find_thread(pthread)) != 0) {
22		/* NOTHING */
23	} else if (pthread->state == PS_DEAD || pthread->state == PS_DEADLOCK) {
24		ret = 0;
25	} else {
26		/* Protect the scheduling queues: */
27		_thread_kern_sig_defer();
28
29		if (((pthread->cancelflags & PTHREAD_CANCEL_DISABLE) != 0) ||
30		    (((pthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) == 0) &&
31		    ((pthread->cancelflags & PTHREAD_AT_CANCEL_POINT) == 0)))
32			/* Just mark it for cancellation: */
33			pthread->cancelflags |= PTHREAD_CANCELLING;
34		else {
35			/*
36			 * Check if we need to kick it back into the
37			 * run queue:
38			 */
39			switch (pthread->state) {
40			case PS_RUNNING:
41				/* No need to resume: */
42				pthread->cancelflags |= PTHREAD_CANCELLING;
43				break;
44
45			case PS_SPINBLOCK:
46			case PS_FDR_WAIT:
47			case PS_FDW_WAIT:
48			case PS_POLL_WAIT:
49			case PS_SELECT_WAIT:
50				/* Remove these threads from the work queue: */
51				if ((pthread->flags & PTHREAD_FLAGS_IN_WORKQ)
52				    != 0)
53					PTHREAD_WORKQ_REMOVE(pthread);
54				/* Fall through: */
55			case PS_SIGTHREAD:
56			case PS_SLEEP_WAIT:
57			case PS_WAIT_WAIT:
58			case PS_SIGSUSPEND:
59			case PS_SIGWAIT:
60				/* Interrupt and resume: */
61				pthread->interrupted = 1;
62				pthread->cancelflags |= PTHREAD_CANCELLING;
63				PTHREAD_NEW_STATE(pthread,PS_RUNNING);
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				}
74				pthread->cancelflags |= PTHREAD_CANCELLING;
75				PTHREAD_NEW_STATE(pthread, PS_RUNNING);
76				break;
77
78			case PS_SUSPENDED:
79				if (pthread->suspended == SUSP_NO ||
80				    pthread->suspended == SUSP_YES ||
81				    pthread->suspended == SUSP_JOIN ||
82				    pthread->suspended == SUSP_NOWAIT) {
83					/*
84					 * This thread isn't in any scheduling
85					 * queues; just change it's state:
86					 */
87					pthread->cancelflags |=
88					    PTHREAD_CANCELLING;
89					PTHREAD_SET_STATE(pthread, PS_RUNNING);
90					break;
91				}
92				/* FALLTHROUGH */
93			case PS_MUTEX_WAIT:
94			case PS_COND_WAIT:
95			case PS_FDLR_WAIT:
96			case PS_FDLW_WAIT:
97			case PS_FILE_WAIT:
98				/*
99				 * Threads in these states may be in queues.
100				 * In order to preserve queue integrity, the
101				 * cancelled thread must remove itself from the
102				 * queue.  Mark the thread as interrupted and
103				 * needing cancellation, and set the state to
104				 * running.  When the thread resumes, it will
105				 * remove itself from the queue and call the
106				 * cancellation completion routine.
107				 */
108				pthread->interrupted = 1;
109				pthread->cancelflags |= PTHREAD_CANCEL_NEEDED;
110				PTHREAD_NEW_STATE(pthread,PS_RUNNING);
111				pthread->continuation = finish_cancellation;
112				break;
113
114			case PS_DEAD:
115			case PS_DEADLOCK:
116			case PS_STATE_MAX:
117				/* Ignore - only here to silence -Wall: */
118				break;
119			}
120		}
121
122		/* Unprotect the scheduling queues: */
123		_thread_kern_sig_undefer();
124
125		ret = 0;
126	}
127	return (ret);
128}
129
130int
131_pthread_setcancelstate(int state, int *oldstate)
132{
133	struct pthread	*curthread = _get_curthread();
134	int ostate;
135	int ret;
136
137	ostate = curthread->cancelflags & PTHREAD_CANCEL_DISABLE;
138
139	switch (state) {
140	case PTHREAD_CANCEL_ENABLE:
141		if (oldstate != NULL)
142			*oldstate = ostate;
143		curthread->cancelflags &= ~PTHREAD_CANCEL_DISABLE;
144		if ((curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) != 0)
145			pthread_testcancel();
146		ret = 0;
147		break;
148	case PTHREAD_CANCEL_DISABLE:
149		if (oldstate != NULL)
150			*oldstate = ostate;
151		curthread->cancelflags |= PTHREAD_CANCEL_DISABLE;
152		ret = 0;
153		break;
154	default:
155		ret = EINVAL;
156	}
157
158	return (ret);
159}
160
161int
162_pthread_setcanceltype(int type, int *oldtype)
163{
164	struct pthread	*curthread = _get_curthread();
165	int otype;
166	int ret;
167
168	otype = curthread->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS;
169	switch (type) {
170	case PTHREAD_CANCEL_ASYNCHRONOUS:
171		if (oldtype != NULL)
172			*oldtype = otype;
173		curthread->cancelflags |= PTHREAD_CANCEL_ASYNCHRONOUS;
174		pthread_testcancel();
175		ret = 0;
176		break;
177	case PTHREAD_CANCEL_DEFERRED:
178		if (oldtype != NULL)
179			*oldtype = otype;
180		curthread->cancelflags &= ~PTHREAD_CANCEL_ASYNCHRONOUS;
181		ret = 0;
182		break;
183	default:
184		ret = EINVAL;
185	}
186
187	return (ret);
188}
189
190void
191_pthread_testcancel(void)
192{
193	struct pthread	*curthread = _get_curthread();
194
195	if (((curthread->cancelflags & PTHREAD_CANCEL_DISABLE) == 0) &&
196	    ((curthread->cancelflags & PTHREAD_CANCELLING) != 0)) {
197		/*
198		 * It is possible for this thread to be swapped out
199		 * while performing cancellation; do not allow it
200		 * to be cancelled again.
201		 */
202		curthread->cancelflags &= ~PTHREAD_CANCELLING;
203		_thread_exit_cleanup();
204		pthread_exit(PTHREAD_CANCELED);
205		PANIC("cancel");
206	}
207}
208
209void
210_thread_enter_cancellation_point(void)
211{
212	struct pthread	*curthread = _get_curthread();
213
214	/* Look for a cancellation before we block: */
215	pthread_testcancel();
216	curthread->cancelflags |= PTHREAD_AT_CANCEL_POINT;
217}
218
219void
220_thread_leave_cancellation_point(void)
221{
222	struct pthread	*curthread = _get_curthread();
223
224	curthread->cancelflags &= ~PTHREAD_AT_CANCEL_POINT;
225	/* Look for a cancellation after we unblock: */
226	pthread_testcancel();
227}
228
229static void
230finish_cancellation(void *arg)
231{
232	struct pthread	*curthread = _get_curthread();
233
234	curthread->continuation = NULL;
235	curthread->interrupted = 0;
236
237	if ((curthread->cancelflags & PTHREAD_CANCEL_NEEDED) != 0) {
238		curthread->cancelflags &= ~PTHREAD_CANCEL_NEEDED;
239		_thread_exit_cleanup();
240		pthread_exit(PTHREAD_CANCELED);
241	}
242}
243