xref: /freebsd/lib/librt/sigev_thread.c (revision 39beb93c3f8bdbf72a61fda42300b5ebed7390c8)
1 /*
2  * Copyright (c) 2005 David Xu <davidxu@freebsd.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice unmodified, this list of conditions, and the following
10  *    disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  *
28  */
29 
30 #include <sys/types.h>
31 #include <machine/atomic.h>
32 
33 #include "namespace.h"
34 #include <err.h>
35 #include <errno.h>
36 #include <ucontext.h>
37 #include <sys/thr.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <signal.h>
42 #include <pthread.h>
43 #include "un-namespace.h"
44 
45 #include "sigev_thread.h"
46 
47 LIST_HEAD(sigev_list_head, sigev_node);
48 #define HASH_QUEUES		17
49 #define	HASH(t, id)		((((id) << 3) + (t)) % HASH_QUEUES)
50 
51 static struct sigev_list_head	sigev_hash[HASH_QUEUES];
52 static struct sigev_list_head	sigev_all;
53 static LIST_HEAD(,sigev_thread)	sigev_threads;
54 static unsigned int		sigev_generation;
55 static pthread_mutex_t		*sigev_list_mtx;
56 static pthread_once_t		sigev_once = PTHREAD_ONCE_INIT;
57 static pthread_once_t		sigev_once_default = PTHREAD_ONCE_INIT;
58 static struct sigev_thread	*sigev_default_thread;
59 static pthread_attr_t		sigev_default_attr;
60 static int			atfork_registered;
61 
62 static void	__sigev_fork_prepare(void);
63 static void	__sigev_fork_parent(void);
64 static void	__sigev_fork_child(void);
65 static struct sigev_thread	*sigev_thread_create(int);
66 static void	*sigev_service_loop(void *);
67 static void	*worker_routine(void *);
68 static void	worker_cleanup(void *);
69 
70 #pragma weak _pthread_create
71 
72 static void
73 attrcopy(pthread_attr_t *src, pthread_attr_t *dst)
74 {
75 	struct sched_param sched;
76 	void *a;
77 	size_t u;
78 	int v;
79 
80 	_pthread_attr_getschedpolicy(src, &v);
81 	_pthread_attr_setschedpolicy(dst, v);
82 
83 	_pthread_attr_getinheritsched(src, &v);
84 	_pthread_attr_setinheritsched(dst, v);
85 
86 	_pthread_attr_getschedparam(src, &sched);
87 	_pthread_attr_setschedparam(dst, &sched);
88 
89 	_pthread_attr_getscope(src, &v);
90 	_pthread_attr_setscope(dst, v);
91 
92 	_pthread_attr_getstacksize(src, &u);
93 	_pthread_attr_setstacksize(dst, u);
94 
95 	_pthread_attr_getstackaddr(src, &a);
96 	_pthread_attr_setstackaddr(src, a);
97 
98 	_pthread_attr_getguardsize(src, &u);
99 	_pthread_attr_setguardsize(dst, u);
100 }
101 
102 static __inline int
103 have_threads(void)
104 {
105 	return (&_pthread_create != NULL);
106 }
107 
108 void
109 __sigev_thread_init(void)
110 {
111 	static int inited = 0;
112 	pthread_mutexattr_t mattr;
113 	int i;
114 
115 	_pthread_mutexattr_init(&mattr);
116 	_pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_NORMAL);
117 	sigev_list_mtx = malloc(sizeof(pthread_mutex_t));
118 	_pthread_mutex_init(sigev_list_mtx, &mattr);
119 	_pthread_mutexattr_destroy(&mattr);
120 
121 	for (i = 0; i < HASH_QUEUES; ++i)
122 		LIST_INIT(&sigev_hash[i]);
123 	LIST_INIT(&sigev_all);
124 	LIST_INIT(&sigev_threads);
125 	sigev_default_thread = NULL;
126 	if (atfork_registered == 0) {
127 		_pthread_atfork(
128 			__sigev_fork_prepare,
129 			__sigev_fork_parent,
130 			__sigev_fork_child);
131 		atfork_registered = 1;
132 	}
133 	if (!inited) {
134 		_pthread_attr_init(&sigev_default_attr);
135 		_pthread_attr_setscope(&sigev_default_attr,
136 			PTHREAD_SCOPE_SYSTEM);
137 		_pthread_attr_setdetachstate(&sigev_default_attr,
138 			PTHREAD_CREATE_DETACHED);
139 		inited = 1;
140 	}
141 	sigev_default_thread = sigev_thread_create(0);
142 }
143 
144 int
145 __sigev_check_init(void)
146 {
147 	if (!have_threads())
148 		return (-1);
149 
150 	_pthread_once(&sigev_once, __sigev_thread_init);
151 	return (sigev_default_thread != NULL) ? 0 : -1;
152 }
153 
154 static void
155 __sigev_fork_prepare(void)
156 {
157 }
158 
159 static void
160 __sigev_fork_parent(void)
161 {
162 }
163 
164 static void
165 __sigev_fork_child(void)
166 {
167 	/*
168 	 * This is a hack, the thread libraries really should
169 	 * check if the handlers were already registered in
170 	 * pthread_atfork().
171 	 */
172 	atfork_registered = 1;
173 	memcpy(&sigev_once, &sigev_once_default, sizeof(sigev_once));
174 	__sigev_thread_init();
175 }
176 
177 void
178 __sigev_list_lock(void)
179 {
180 	_pthread_mutex_lock(sigev_list_mtx);
181 }
182 
183 void
184 __sigev_list_unlock(void)
185 {
186 	_pthread_mutex_unlock(sigev_list_mtx);
187 }
188 
189 struct sigev_node *
190 __sigev_alloc(int type, const struct sigevent *evp, struct sigev_node *prev,
191 	int usedefault)
192 {
193 	struct sigev_node *sn;
194 
195 	sn = calloc(1, sizeof(*sn));
196 	if (sn != NULL) {
197 		sn->sn_value = evp->sigev_value;
198 		sn->sn_func  = evp->sigev_notify_function;
199 		sn->sn_gen   = atomic_fetchadd_int(&sigev_generation, 1);
200 		sn->sn_type  = type;
201 		_pthread_attr_init(&sn->sn_attr);
202 		_pthread_attr_setdetachstate(&sn->sn_attr, PTHREAD_CREATE_DETACHED);
203 		if (evp->sigev_notify_attributes)
204 			attrcopy(evp->sigev_notify_attributes, &sn->sn_attr);
205 		if (prev) {
206 			__sigev_list_lock();
207 			prev->sn_tn->tn_refcount++;
208 			__sigev_list_unlock();
209 			sn->sn_tn = prev->sn_tn;
210 		} else {
211 			sn->sn_tn = sigev_thread_create(usedefault);
212 			if (sn->sn_tn == NULL) {
213 				_pthread_attr_destroy(&sn->sn_attr);
214 				free(sn);
215 				sn = NULL;
216 			}
217 		}
218 	}
219 	return (sn);
220 }
221 
222 void
223 __sigev_get_sigevent(struct sigev_node *sn, struct sigevent *newevp,
224 	sigev_id_t id)
225 {
226 	/*
227 	 * Build a new sigevent, and tell kernel to deliver SIGSERVICE
228 	 * signal to the new thread.
229 	 */
230 	newevp->sigev_notify = SIGEV_THREAD_ID;
231 	newevp->sigev_signo  = SIGSERVICE;
232 	newevp->sigev_notify_thread_id = (lwpid_t)sn->sn_tn->tn_lwpid;
233 	newevp->sigev_value.sival_ptr = (void *)id;
234 }
235 
236 void
237 __sigev_free(struct sigev_node *sn)
238 {
239 	_pthread_attr_destroy(&sn->sn_attr);
240 	free(sn);
241 }
242 
243 struct sigev_node *
244 __sigev_find(int type, sigev_id_t id)
245 {
246 	struct sigev_node *sn;
247 	int chain = HASH(type, id);
248 
249 	LIST_FOREACH(sn, &sigev_hash[chain], sn_link) {
250 		if (sn->sn_type == type && sn->sn_id == id)
251 			break;
252 	}
253 	return (sn);
254 }
255 
256 int
257 __sigev_register(struct sigev_node *sn)
258 {
259 	int chain = HASH(sn->sn_type, sn->sn_id);
260 
261 	LIST_INSERT_HEAD(&sigev_hash[chain], sn, sn_link);
262 	return (0);
263 }
264 
265 int
266 __sigev_delete(int type, sigev_id_t id)
267 {
268 	struct sigev_node *sn;
269 
270 	sn = __sigev_find(type, id);
271 	if (sn != NULL)
272 		return (__sigev_delete_node(sn));
273 	return (0);
274 }
275 
276 int
277 __sigev_delete_node(struct sigev_node *sn)
278 {
279 	LIST_REMOVE(sn, sn_link);
280 
281 	if (--sn->sn_tn->tn_refcount == 0)
282 		_pthread_kill(sn->sn_tn->tn_thread, SIGSERVICE);
283 	if (sn->sn_flags & SNF_WORKING)
284 		sn->sn_flags |= SNF_REMOVED;
285 	else
286 		__sigev_free(sn);
287 	return (0);
288 }
289 
290 static sigev_id_t
291 sigev_get_id(siginfo_t *si)
292 {
293 	switch(si->si_code) {
294 	case SI_TIMER:
295 		return (si->si_timerid);
296 	case SI_MESGQ:
297 		return (si->si_mqd);
298 	case SI_ASYNCIO:
299 		return (sigev_id_t)si->si_value.sival_ptr;
300 	}
301 	return (-1);
302 }
303 
304 static struct sigev_thread *
305 sigev_thread_create(int usedefault)
306 {
307 	struct sigev_thread *tn;
308 	sigset_t set, oset;
309 	int ret;
310 
311 	if (usedefault && sigev_default_thread) {
312 		__sigev_list_lock();
313 		sigev_default_thread->tn_refcount++;
314 		__sigev_list_unlock();
315 		return (sigev_default_thread);
316 	}
317 
318 	tn = malloc(sizeof(*tn));
319 	tn->tn_cur = NULL;
320 	tn->tn_lwpid = -1;
321 	tn->tn_refcount = 1;
322 	_pthread_cond_init(&tn->tn_cv, NULL);
323 
324 	/* for debug */
325 	__sigev_list_lock();
326 	LIST_INSERT_HEAD(&sigev_threads, tn, tn_link);
327 	__sigev_list_unlock();
328 
329 	sigfillset(&set);	/* SIGSERVICE is masked. */
330 	sigdelset(&set, SIGBUS);
331 	sigdelset(&set, SIGILL);
332 	sigdelset(&set, SIGFPE);
333 	sigdelset(&set, SIGSEGV);
334 	sigdelset(&set, SIGTRAP);
335 	_sigprocmask(SIG_SETMASK, &set, &oset);
336 	ret = _pthread_create(&tn->tn_thread, &sigev_default_attr,
337 		 sigev_service_loop, tn);
338 	_sigprocmask(SIG_SETMASK, &oset, NULL);
339 
340 	if (ret != 0) {
341 		__sigev_list_lock();
342 		LIST_REMOVE(tn, tn_link);
343 		__sigev_list_unlock();
344 		free(tn);
345 		tn = NULL;
346 	} else {
347 		/* wait the thread to get its lwpid */
348 
349 		__sigev_list_lock();
350 		while (tn->tn_lwpid == -1)
351 			_pthread_cond_wait(&tn->tn_cv, sigev_list_mtx);
352 		__sigev_list_unlock();
353 	}
354 	return (tn);
355 }
356 
357 /*
358  * The thread receives notification from kernel and creates
359  * a thread to call user callback function.
360  */
361 static void *
362 sigev_service_loop(void *arg)
363 {
364 	static int failure;
365 
366 	siginfo_t si;
367 	sigset_t set;
368 	struct sigev_thread *tn;
369 	struct sigev_node *sn;
370 	sigev_id_t id;
371 	pthread_t td;
372 	int ret;
373 
374 	tn = arg;
375 	thr_self(&tn->tn_lwpid);
376 	__sigev_list_lock();
377 	_pthread_cond_broadcast(&tn->tn_cv);
378 	__sigev_list_unlock();
379 
380 	sigemptyset(&set);
381 	sigaddset(&set, SIGSERVICE);
382 	for (;;) {
383 		ret = sigwaitinfo(&set, &si);
384 
385 		__sigev_list_lock();
386 		if (tn->tn_refcount == 0) {
387 			LIST_REMOVE(tn, tn_link);
388 			__sigev_list_unlock();
389 			free(tn);
390 			break;
391 		}
392 
393 		if (ret == -1) {
394 			__sigev_list_unlock();
395 			continue;
396 		}
397 
398 		id = sigev_get_id(&si);
399 		sn = __sigev_find(si.si_code, id);
400 		if (sn == NULL) {
401 			__sigev_list_unlock();
402 			continue;
403 		}
404 
405 		sn->sn_info = si;
406 		if (sn->sn_flags & SNF_SYNC)
407 			tn->tn_cur = sn;
408 		else
409 			tn->tn_cur = NULL;
410 		sn->sn_flags |= SNF_WORKING;
411 		__sigev_list_unlock();
412 
413 		ret = _pthread_create(&td, &sn->sn_attr, worker_routine, sn);
414 		if (ret != 0) {
415 			if (failure++ < 5)
416 				warnc(ret, "%s:%s failed to create thread.\n",
417 					__FILE__, __func__);
418 
419 			__sigev_list_lock();
420 			sn->sn_flags &= ~SNF_WORKING;
421 			if (sn->sn_flags & SNF_REMOVED)
422 				__sigev_free(sn);
423 			__sigev_list_unlock();
424 		} else if (tn->tn_cur) {
425 			__sigev_list_lock();
426 			while (tn->tn_cur)
427 				_pthread_cond_wait(&tn->tn_cv, sigev_list_mtx);
428 			__sigev_list_unlock();
429 		}
430 	}
431 	return (0);
432 }
433 
434 /*
435  * newly created worker thread to call user callback function.
436  */
437 static void *
438 worker_routine(void *arg)
439 {
440 	struct sigev_node *sn = arg;
441 
442 	_pthread_cleanup_push(worker_cleanup, sn);
443 	sn->sn_dispatch(sn);
444 	_pthread_cleanup_pop(1);
445 
446 	return (0);
447 }
448 
449 /* clean up a notification after dispatch. */
450 static void
451 worker_cleanup(void *arg)
452 {
453 	struct sigev_node *sn = arg;
454 
455 	__sigev_list_lock();
456 	if (sn->sn_flags & SNF_SYNC) {
457 		sn->sn_tn->tn_cur = NULL;
458 		_pthread_cond_broadcast(&sn->sn_tn->tn_cv);
459 	}
460 	if (sn->sn_flags & SNF_REMOVED)
461 		__sigev_free(sn);
462 	else
463 		sn->sn_flags &= ~SNF_WORKING;
464 	__sigev_list_unlock();
465 }
466