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