1 /*****************************************************************************
2 * Written by Chris Dunlap <cdunlap@llnl.gov>.
3 * Copyright (C) 2007-2020 Lawrence Livermore National Security, LLC.
4 * Copyright (C) 2002-2007 The Regents of the University of California.
5 * UCRL-CODE-155910.
6 *
7 * This file is part of the MUNGE Uid 'N' Gid Emporium (MUNGE).
8 * For details, see <https://dun.github.io/munge/>.
9 *
10 * MUNGE is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free
12 * Software Foundation, either version 3 of the License, or (at your option)
13 * any later version. Additionally for the MUNGE library (libmunge), you
14 * can redistribute it and/or modify it under the terms of the GNU Lesser
15 * General Public License as published by the Free Software Foundation,
16 * either version 3 of the License, or (at your option) any later version.
17 *
18 * MUNGE is distributed in the hope that it will be useful, but WITHOUT
19 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 * and GNU Lesser General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * and GNU Lesser General Public License along with MUNGE. If not, see
25 * <http://www.gnu.org/licenses/>.
26 *****************************************************************************
27 * Based on ideas from:
28 * - David R. Butenhof's "Programming with POSIX Threads" (Section 3.3.4)
29 * - Jon C. Snader's "Effective TCP/IP Programming" (Tip #20)
30 *****************************************************************************
31 * Refer to "timer.h" for documentation on public functions.
32 *****************************************************************************/
33
34
35 #if HAVE_CONFIG_H
36 # include <config.h>
37 #endif /* HAVE_CONFIG_H */
38
39 #include <assert.h>
40 #include <errno.h>
41 #include <pthread.h>
42 #include <signal.h>
43 #include <stdlib.h>
44 #include <sys/time.h>
45 #include <time.h>
46 #include <unistd.h>
47 #include <munge.h>
48 #include "log.h"
49 #include "thread.h"
50 #include "timer.h"
51
52
53 /*****************************************************************************
54 * Private Data Types
55 *****************************************************************************/
56
57 struct timer {
58 long id; /* timer ID */
59 struct timespec ts; /* expiration time */
60 callback_f f; /* callback function */
61 void *arg; /* callback function arg */
62 struct timer *next; /* next timer in list */
63 };
64
65 typedef struct timer * timer_p;
66
67
68 /*****************************************************************************
69 * Private Prototypes
70 *****************************************************************************/
71
72 static void * _timer_thread (void *arg);
73
74 static void _timer_thread_cleanup (void *arg);
75
76 static timer_p _timer_alloc (void);
77
78 static void _timer_get_timespec (struct timespec *tsp);
79
80 static int _timer_is_timespec_ge (
81 struct timespec *tsp0, struct timespec *tsp1);
82
83
84 /*****************************************************************************
85 * Private Variables
86 *****************************************************************************/
87
88 static pthread_t _timer_tid = 0;
89 static pthread_cond_t _timer_cond = PTHREAD_COND_INITIALIZER;
90 static pthread_mutex_t _timer_mutex = PTHREAD_MUTEX_INITIALIZER;
91
92 /* The _timer_id is the ID of the last timer that was set.
93 */
94 static long _timer_id = 0;
95
96 /* The _timer_active list contains timers waiting to be dispatched, sorted in
97 * order of increasing timespecs; the list head is the next timer to expire.
98 */
99 static timer_p _timer_active = NULL;
100
101 /* The _timer_inactive list contains timers that have been dispatched and can
102 * be reused without allocating more memory.
103 */
104 static timer_p _timer_inactive = NULL;
105
106
107 /*****************************************************************************
108 * Public Functions
109 *****************************************************************************/
110
111 void
timer_init(void)112 timer_init (void)
113 {
114 pthread_attr_t tattr;
115 size_t stacksize = 256 * 1024;
116
117 if (_timer_tid != 0) {
118 return;
119 }
120 if ((errno = pthread_attr_init (&tattr)) != 0) {
121 log_errno (EMUNGE_SNAFU, LOG_ERR,
122 "Failed to init timer thread attribute");
123 }
124 #ifdef _POSIX_THREAD_ATTR_STACKSIZE
125 if ((errno = pthread_attr_setstacksize (&tattr, stacksize)) != 0) {
126 log_errno (EMUNGE_SNAFU, LOG_ERR,
127 "Failed to set timer thread stacksize");
128 }
129 if ((errno = pthread_attr_getstacksize (&tattr, &stacksize)) != 0) {
130 log_errno (EMUNGE_SNAFU, LOG_ERR,
131 "Failed to get timer thread stacksize");
132 }
133 log_msg (LOG_DEBUG, "Set timer thread stacksize to %d", (int) stacksize);
134 #endif /* _POSIX_THREAD_ATTR_STACKSIZE */
135
136 if ((errno = pthread_create (&_timer_tid, &tattr, _timer_thread, NULL))
137 != 0) {
138 log_errno (EMUNGE_SNAFU, LOG_ERR,
139 "Failed to create timer thread");
140 }
141 if ((errno = pthread_attr_destroy (&tattr)) != 0) {
142 log_errno (EMUNGE_SNAFU, LOG_ERR,
143 "Failed to destroy timer thread attribute");
144 }
145 return;
146 }
147
148
149 void
timer_fini(void)150 timer_fini (void)
151 {
152 void *result;
153 timer_p *t_prev_ptr;
154 timer_p t;
155
156 if (_timer_tid == 0) {
157 return;
158 }
159 if ((errno = pthread_cancel (_timer_tid)) != 0) {
160 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to cancel timer thread");
161 }
162 if ((errno = pthread_join (_timer_tid, &result)) != 0) {
163 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to join timer thread");
164 }
165 if (result != PTHREAD_CANCELED) {
166 log_err (EMUNGE_SNAFU, LOG_ERR, "Timer thread was not canceled");
167 }
168 _timer_tid = 0;
169
170 if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
171 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock timer mutex");
172 }
173 /* Cancel pending timers by moving active timers to the inactive list.
174 */
175 if (_timer_active) {
176 t_prev_ptr = &_timer_active;
177 while (*t_prev_ptr) {
178 t_prev_ptr = &(*t_prev_ptr)->next;
179 }
180 *t_prev_ptr = _timer_inactive;
181 _timer_inactive = _timer_active;
182 _timer_active = NULL;
183 }
184 /* De-allocate timers.
185 */
186 while (_timer_inactive) {
187 t = _timer_inactive;
188 _timer_inactive = _timer_inactive->next;
189 free (t);
190 }
191 if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
192 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock timer mutex");
193 }
194 return;
195 }
196
197
198 long
timer_set_absolute(callback_f cb,void * arg,const struct timespec * tsp)199 timer_set_absolute (callback_f cb, void *arg, const struct timespec *tsp)
200 {
201 timer_p t;
202 timer_p *t_prev_ptr;
203 int do_signal = 0;
204
205 if (!cb || !tsp) {
206 errno = EINVAL;
207 return (-1);
208 }
209 if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
210 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock timer mutex");
211 }
212 if (!(t = _timer_alloc ())) {
213 log_errno (EMUNGE_NO_MEMORY, LOG_ERR, "Failed to allocate timer");
214 }
215 /* Initialize the timer.
216 */
217 _timer_id++;
218 if (_timer_id <= 0) {
219 _timer_id = 1;
220 }
221 t->id = _timer_id;
222 t->f = cb;
223 t->arg = arg;
224 t->ts = *tsp;
225
226 /* Insert the timer into the active list.
227 */
228 t_prev_ptr = &_timer_active;
229 while (*t_prev_ptr && _timer_is_timespec_ge (&t->ts, &(*t_prev_ptr)->ts)) {
230 t_prev_ptr = &(*t_prev_ptr)->next;
231 }
232 t->next = *t_prev_ptr;
233 *t_prev_ptr = t;
234
235 /* Only signal the timer thread if the active timer has changed.
236 * Set a flag here so the signal can be done outside the monitor lock.
237 */
238 if (t_prev_ptr == &_timer_active) {
239 do_signal = 1;
240 }
241 if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
242 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock timer mutex");
243 }
244 if (do_signal) {
245 if ((errno = pthread_cond_signal (&_timer_cond)) != 0) {
246 log_errno (EMUNGE_SNAFU, LOG_ERR,
247 "Failed to signal timer condition");
248 }
249 }
250 assert (t->id > 0);
251 return (t->id);
252 }
253
254
255 long
timer_set_relative(callback_f cb,void * arg,long msec)256 timer_set_relative (callback_f cb, void *arg, long msec)
257 {
258 struct timespec ts;
259
260 /* Convert the relative time offset into an absolute timespec from now.
261 */
262 _timer_get_timespec (&ts);
263
264 if (msec > 0) {
265 ts.tv_sec += msec / 1000;
266 ts.tv_nsec += (msec % 1000) * 1000000;
267 if (ts.tv_nsec >= 1000000000) {
268 ts.tv_sec += ts.tv_nsec / 1000000000;
269 ts.tv_nsec %= 1000000000;
270 }
271 }
272 return (timer_set_absolute (cb, arg, &ts));
273 }
274
275
276 int
timer_cancel(long id)277 timer_cancel (long id)
278 {
279 timer_p *t_prev_ptr;
280 timer_p t = NULL;
281 int do_signal = 0;
282
283 if (id <= 0) {
284 errno = EINVAL;
285 return (-1);
286 }
287 if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
288 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock timer mutex");
289 }
290 /* Locate the active timer specified by [id].
291 */
292 t_prev_ptr = &_timer_active;
293 while (*t_prev_ptr && (id != (*t_prev_ptr)->id)) {
294 t_prev_ptr = &(*t_prev_ptr)->next;
295 }
296 /* Remove the located timer from the active list.
297 */
298 if (*t_prev_ptr) {
299 t = *t_prev_ptr;
300 *t_prev_ptr = t->next;
301 t->next = _timer_inactive;
302 _timer_inactive = t;
303 /*
304 * Only signal the timer thread if the active timer was canceled.
305 * Set a flag here so the signal can be done outside the monitor lock.
306 */
307 if (t_prev_ptr == &_timer_active) {
308 do_signal = 1;
309 }
310 }
311 if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
312 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock timer mutex");
313 }
314 if (do_signal) {
315 if ((errno = pthread_cond_signal (&_timer_cond)) != 0) {
316 log_errno (EMUNGE_SNAFU, LOG_ERR,
317 "Failed to signal timer condition");
318 }
319 }
320 return (t ? 1 : 0);
321 }
322
323
324 /*****************************************************************************
325 * Private Functions
326 *****************************************************************************/
327
328 static void *
_timer_thread(void * arg)329 _timer_thread (void *arg)
330 {
331 /* The timer thread. It waits until the next active timer expires,
332 * at which point it invokes the timer's callback function.
333 */
334 sigset_t sigset;
335 int cancel_state;
336 struct timespec ts_now;
337 timer_p *t_prev_ptr;
338 timer_p timer_expired;
339
340 if (sigfillset (&sigset)) {
341 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to init timer sigset");
342 }
343 if (pthread_sigmask (SIG_SETMASK, &sigset, NULL) != 0) {
344 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to set timer sigset");
345 }
346 if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
347 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to lock timer mutex");
348 }
349 pthread_cleanup_push (_timer_thread_cleanup, NULL);
350
351 for (;;) {
352 /*
353 * Wait until a timer has been added to the active list.
354 */
355 while (!_timer_active) {
356 /*
357 * Cancellation point.
358 */
359 if ((errno = pthread_cond_wait (&_timer_cond, &_timer_mutex)) != 0)
360 {
361 log_errno (EMUNGE_SNAFU, LOG_ERR,
362 "Failed to wait on timer condition");
363 }
364 }
365 /* Disable the thread's cancellation state in case any
366 * callback functions contain cancellation points.
367 */
368 errno = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &cancel_state);
369 if (errno != 0) {
370 log_errno (EMUNGE_SNAFU, LOG_ERR,
371 "Failed to disable timer thread cancellation");
372 }
373 _timer_get_timespec (&ts_now);
374
375 /* Select expired timers.
376 */
377 t_prev_ptr = &_timer_active;
378 while (*t_prev_ptr
379 && _timer_is_timespec_ge (&ts_now, &(*t_prev_ptr)->ts)) {
380 t_prev_ptr = &(*t_prev_ptr)->next;
381 }
382 if (t_prev_ptr != &_timer_active) {
383 /*
384 * Move expired timers from the active list onto an expired list.
385 * All expired timers are dispatched before the active list is
386 * rescanned. This protects against an erroneous ts_now set in
387 * the future from causing recurring timers to be continually
388 * dispatched since ts_now will be requeried once the expired
389 * list is processed. (Issue 15)
390 */
391 timer_expired = _timer_active;
392 _timer_active = *t_prev_ptr;
393 *t_prev_ptr = NULL;
394 /*
395 * Unlock the mutex while dispatching callback functions in case
396 * any need to set/cancel timers.
397 */
398 if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
399 log_errno (EMUNGE_SNAFU, LOG_ERR,
400 "Failed to unlock timer mutex");
401 }
402 /* Dispatch expired timers.
403 */
404 t_prev_ptr = &timer_expired;
405 while (*t_prev_ptr) {
406 (*t_prev_ptr)->f ((*t_prev_ptr)->arg);
407 t_prev_ptr = &(*t_prev_ptr)->next;
408 }
409 if ((errno = pthread_mutex_lock (&_timer_mutex)) != 0) {
410 log_errno (EMUNGE_SNAFU, LOG_ERR,
411 "Failed to lock timer mutex");
412 }
413 /* Move the expired timers onto the inactive list.
414 * At the end of the previous while-loop, t_prev_ptr is the
415 * address of the terminating NULL of the timer_expired list.
416 */
417 *t_prev_ptr = _timer_inactive;
418 _timer_inactive = timer_expired;
419 }
420 /* Enable the thread's cancellation state.
421 * Since enabling cancellation is not a cancellation point,
422 * a pending cancel request must be tested for. But a
423 * pthread_testcancel() is not needed here. If active timers
424 * are present, the pthread_cond_timedwait() at the bottom of
425 * the for-loop will serve as the cancellation point; otherwise,
426 * the pthread_cond_wait() at the top of the for-loop will.
427 */
428 errno = pthread_setcancelstate (cancel_state, &cancel_state);
429 if (errno != 0) {
430 log_errno (EMUNGE_SNAFU, LOG_ERR,
431 "Failed to enable timer thread cancellation");
432 }
433 /* Wait until the next active timer is set to expire,
434 * or until the active timer changes.
435 */
436 while (_timer_active) {
437 /*
438 * Cancellation point.
439 */
440 errno = pthread_cond_timedwait (
441 &_timer_cond, &_timer_mutex, &(_timer_active->ts));
442
443 if (errno == EINTR) {
444 continue;
445 }
446 if ((errno == ETIMEDOUT) || (errno == 0)) {
447 break;
448 }
449 log_errno (EMUNGE_SNAFU, LOG_ERR,
450 "Failed to wait on timer condition");
451 }
452 }
453 assert (1); /* not reached */
454 pthread_cleanup_pop (1);
455 return (NULL);
456 }
457
458
459 static void
_timer_thread_cleanup(void * arg)460 _timer_thread_cleanup (void *arg)
461 {
462 /* The cleanup routine for the timer thread.
463 * It ensures the mutex is released when the thread is canceled.
464 */
465 if ((errno = pthread_mutex_unlock (&_timer_mutex)) != 0) {
466 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to unlock timer mutex");
467 }
468 return;
469 }
470
471
472 static timer_p
_timer_alloc(void)473 _timer_alloc (void)
474 {
475 /* Returns a new timer, or NULL on memory allocation failure.
476 * The mutex must be locked before calling this routine.
477 */
478 timer_p t;
479
480 assert (lsd_mutex_is_locked (&_timer_mutex));
481
482 if (_timer_inactive) {
483 t = _timer_inactive;
484 _timer_inactive = _timer_inactive->next;
485 t->next = NULL;
486 }
487 else {
488 t = malloc (sizeof (struct timer));
489 }
490 return (t);
491 }
492
493
494 static void
_timer_get_timespec(struct timespec * tsp)495 _timer_get_timespec (struct timespec *tsp)
496 {
497 /* Sets the timespec [tsp] to the current time.
498 */
499 struct timeval tv;
500
501 assert (tsp != NULL);
502
503 if (gettimeofday (&tv, NULL) < 0) {
504 log_errno (EMUNGE_SNAFU, LOG_ERR, "Failed to query current time");
505 }
506 tsp->tv_sec = tv.tv_sec;
507 tsp->tv_nsec = tv.tv_usec * 1000;
508 return;
509 }
510
511
512 static int
_timer_is_timespec_ge(struct timespec * tsp0,struct timespec * tsp1)513 _timer_is_timespec_ge (struct timespec *tsp0, struct timespec *tsp1)
514 {
515 /* Returns non-zero if the time specified by [tsp0] is
516 * greater than or equal to the time specified by [tsp1].
517 */
518 assert (tsp0 != NULL);
519 assert (tsp1 != NULL);
520
521 if (tsp0->tv_nsec >= 1000000000) {
522 tsp0->tv_sec += tsp0->tv_nsec / 1000000000;
523 tsp0->tv_nsec %= 1000000000;
524 }
525 if (tsp1->tv_nsec >= 1000000000) {
526 tsp1->tv_sec += tsp1->tv_nsec / 1000000000;
527 tsp1->tv_nsec %= 1000000000;
528 }
529 if (tsp0->tv_sec == tsp1->tv_sec) {
530 return (tsp0->tv_nsec >= tsp1->tv_nsec);
531 }
532 else {
533 return (tsp0->tv_sec >= tsp1->tv_sec);
534 }
535 }
536