add emacs indentation variables to source files in line with current vim settings
[monky] / src / timed_thread.c
1 /* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
2  *
3  * timed_thread.c: Abstraction layer for timed threads
4  *
5  * Copyright (C) 2006-2007 Philip Kovacs pkovacs@users.sourceforge.net
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA.
21  *
22  * vim: ts=4 sw=4 noet ai cindent syntax=c
23  *
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29
30 #include <pthread.h>
31 #include <assert.h>
32 #include <errno.h>
33 #include <time.h>
34 #include <unistd.h>
35 #ifndef HAVE_CLOCK_GETTIME
36 #include <sys/time.h>
37 #endif
38 #include "timed_thread.h"
39
40 /* Abstraction layer for timed threads */
41
42 static int now(struct timespec *);
43
44 /* private */
45 struct _timed_thread {
46         pthread_t thread;                               /* thread itself */
47         pthread_attr_t thread_attr;             /* thread attributes */
48         pthread_mutex_t cs_mutex;               /* critical section mutex */
49         pthread_mutex_t runnable_mutex; /* only for the runnable_cond */
50         pthread_cond_t runnable_cond;   /* signalled to stop the thread */
51         void *(*start_routine)(void *); /* thread function to run */
52         void *arg;                                              /* thread function argument */
53         struct timespec interval_time;  /* interval_usecs as a struct timespec */
54         struct timespec wait_time;              /* absolute future time next timed_thread_test will wait until */
55         int pipefd[2];
56         int die;
57 };
58
59 /* linked list of created threads */
60 typedef struct _timed_thread_list {
61         timed_thread *p_timed_thread;
62         timed_thread **addr_of_p_timed_thread;
63         struct _timed_thread_list *next;
64 } timed_thread_node, timed_thread_list;
65
66 static timed_thread_list *p_timed_thread_list_head = NULL;
67 static timed_thread_list *p_timed_thread_list_tail = NULL;
68
69 int timed_thread_readfd(timed_thread *p_timed_thread)
70 {
71         return p_timed_thread->pipefd[0];
72 }
73
74 static int now(struct timespec *abstime)
75 {
76 #ifndef HAVE_CLOCK_GETTIME
77         struct timeval tv;
78 #endif
79
80         if (!abstime) {
81                 return -1;
82         }
83
84 #ifdef HAVE_CLOCK_GETTIME
85         return clock_gettime(CLOCK_REALTIME, abstime);
86 #else
87         /* fallback to gettimeofday () */
88         if (gettimeofday(&tv, NULL) != 0) {
89                 return -1;
90         }
91
92         abstime->tv_sec = tv.tv_sec;
93         abstime->tv_nsec = tv.tv_usec * 1000;
94         return 0;
95 #endif
96 }
97
98 /* create a timed thread (object creation only) */
99 timed_thread *timed_thread_create(void *start_routine(void *), void *arg,
100                 unsigned int interval_usecs)
101 {
102         timed_thread *p_timed_thread;
103
104         assert(start_routine != NULL);
105         assert(interval_usecs >= MINIMUM_INTERVAL_USECS);
106
107         if ((p_timed_thread = calloc(sizeof(timed_thread), 1)) == 0) {
108                 return NULL;
109         }
110
111         /* create thread pipe (used to tell threads to die) */
112         if (pipe(p_timed_thread->pipefd)) {
113                 return NULL;
114         }
115
116         /* init attributes, e.g. joinable thread */
117         pthread_attr_init(&p_timed_thread->thread_attr);
118         pthread_attr_setdetachstate(&p_timed_thread->thread_attr,
119                         PTHREAD_CREATE_JOINABLE);
120         /* init mutexes */
121         pthread_mutex_init(&p_timed_thread->cs_mutex, NULL);
122         pthread_mutex_init(&p_timed_thread->runnable_mutex, NULL);
123         /* init cond */
124         pthread_cond_init(&p_timed_thread->runnable_cond, NULL);
125
126         p_timed_thread->start_routine = start_routine;
127         p_timed_thread->arg = arg;
128
129         /* set wait time to current time */
130         if (now(&p_timed_thread->wait_time)) {
131                 return NULL;
132         }
133
134         /* seconds portion of the microseconds interval */
135         p_timed_thread->interval_time.tv_sec = (time_t) (interval_usecs / 1000000);
136         /* remaining microseconds convert to nanoseconds */
137         p_timed_thread->interval_time.tv_nsec =
138                 (long) ((interval_usecs % 1000000) * 1000);
139
140         /* printf("interval_time.tv_sec = %li, .tv_nsec = %li\n",
141            p_timed_thread->interval_time.tv_sec,
142            p_timed_thread->interval_time.tv_nsec); */
143         return p_timed_thread;
144 }
145
146 /* run a timed thread (drop the thread and run it) */
147 int timed_thread_run(timed_thread *p_timed_thread)
148 {
149         return pthread_create(&p_timed_thread->thread, &p_timed_thread->thread_attr,
150                         p_timed_thread->start_routine, p_timed_thread->arg);
151 }
152
153 /* destroy a timed thread.
154  * optional addr_of_p_timed_thread to set callers pointer to NULL as a
155  * convenience. */
156 void timed_thread_destroy(timed_thread *p_timed_thread,
157                 timed_thread **addr_of_p_timed_thread)
158 {
159         assert(p_timed_thread != NULL);
160         assert((addr_of_p_timed_thread == NULL)
161                         || (*addr_of_p_timed_thread == p_timed_thread));
162
163         /* signal thread to stop */
164         pthread_mutex_lock(&p_timed_thread->runnable_mutex);
165         pthread_cond_signal(&p_timed_thread->runnable_cond);
166         p_timed_thread->die = 1;
167         pthread_mutex_unlock(&p_timed_thread->runnable_mutex);
168         write(p_timed_thread->pipefd[1], "die", 3);
169
170         /* join the terminating thread */
171         if (p_timed_thread->thread) {
172                 pthread_join(p_timed_thread->thread, NULL);
173         }
174
175         /* clean up */
176         pthread_attr_destroy(&p_timed_thread->thread_attr);
177         pthread_mutex_destroy(&p_timed_thread->cs_mutex);
178         pthread_mutex_destroy(&p_timed_thread->runnable_mutex);
179         pthread_cond_destroy(&p_timed_thread->runnable_cond);
180
181         free(p_timed_thread);
182         if (addr_of_p_timed_thread) {
183                 *addr_of_p_timed_thread = NULL;
184         }
185 }
186
187 /* lock a timed thread for critical section activity */
188 int timed_thread_lock(timed_thread *p_timed_thread)
189 {
190         assert(p_timed_thread != NULL);
191
192         return pthread_mutex_lock(&p_timed_thread->cs_mutex);
193 }
194
195 /* unlock a timed thread after critical section activity */
196 int timed_thread_unlock(timed_thread *p_timed_thread)
197 {
198         assert(p_timed_thread != NULL);
199
200         return pthread_mutex_unlock(&p_timed_thread->cs_mutex);
201 }
202
203 /* thread waits interval_usecs for runnable_cond to be signaled.
204  * returns 1 if signaled, -1 on error, and 0 otherwise.
205  * caller should call timed_thread_exit() on any non-zero return value. */
206 int timed_thread_test(timed_thread *p_timed_thread, int override_wait_time)
207 {
208         struct timespec now_time;
209         int rc;
210
211         assert(p_timed_thread != NULL);
212
213         /* acquire runnable_cond mutex */
214         if (pthread_mutex_lock(&p_timed_thread->runnable_mutex)) {
215                 /* could not acquire runnable_cond mutex,
216                  * so tell caller to exit thread */
217                 return -1;
218         }
219
220         if (p_timed_thread->die) {
221                 /* if we were kindly asked to die, then die */
222                 return 1;
223         }
224
225         if (override_wait_time && now(&p_timed_thread->wait_time)) {
226                 return -1;
227         }
228
229         /* release mutex and wait until future time for runnable_cond to signal */
230         rc = pthread_cond_timedwait(&p_timed_thread->runnable_cond,
231                         &p_timed_thread->runnable_mutex, &p_timed_thread->wait_time);
232         /* mutex re-acquired, so release it */
233         pthread_mutex_unlock(&p_timed_thread->runnable_mutex);
234         
235         if (now(&now_time)) {
236                 return -1;
237         }
238
239         if (rc == 0) {
240                 /* runnable_cond was signaled, so tell caller to exit thread */
241                 return 1;
242         }
243
244         /* absolute future time for next pass */
245         p_timed_thread->wait_time.tv_sec += p_timed_thread->interval_time.tv_sec;
246         p_timed_thread->wait_time.tv_nsec += p_timed_thread->interval_time.tv_nsec;
247         p_timed_thread->wait_time.tv_sec += p_timed_thread->wait_time.tv_nsec / 1000000000;
248         p_timed_thread->wait_time.tv_nsec = p_timed_thread->wait_time.tv_nsec % 1000000000;
249
250         /* ensure our future wait time is sane */
251         if (p_timed_thread->wait_time.tv_sec > (now_time.tv_sec + p_timed_thread->interval_time.tv_sec) || p_timed_thread->wait_time.tv_sec < now_time.tv_sec) {
252                 p_timed_thread->wait_time.tv_sec = now_time.tv_sec + p_timed_thread->interval_time.tv_sec;
253                 p_timed_thread->wait_time.tv_nsec = now_time.tv_nsec + p_timed_thread->interval_time.tv_nsec;
254                 p_timed_thread->wait_time.tv_sec += p_timed_thread->wait_time.tv_nsec / 1000000000;
255                 p_timed_thread->wait_time.tv_nsec = p_timed_thread->wait_time.tv_nsec % 1000000000;
256         }
257
258         /* tell caller not to exit yet */
259         return 0;
260 }
261
262 /* exit a timed thread */
263 void timed_thread_exit(timed_thread *p_timed_thread)
264 {
265         assert(p_timed_thread != NULL);
266
267         close(p_timed_thread->pipefd[0]);
268         close(p_timed_thread->pipefd[1]);
269
270         pthread_exit(NULL);
271 }
272
273 /* register a timed thread for future destruction via
274  * timed_thread_destroy_registered_threads() */
275 int timed_thread_register(timed_thread *p_timed_thread,
276                 timed_thread **addr_of_p_timed_thread)
277 {
278         timed_thread_node *p_node;
279
280         assert((addr_of_p_timed_thread == NULL)
281                         || (*addr_of_p_timed_thread == p_timed_thread));
282
283         if ((p_node = calloc(sizeof(timed_thread_node), 1)) == 0) {
284                 return 0;
285         }
286
287         p_node->p_timed_thread = p_timed_thread;
288         p_node->addr_of_p_timed_thread = addr_of_p_timed_thread;
289         p_node->next = NULL;
290
291         if (!p_timed_thread_list_tail) {
292                 /* first node of empty list */
293                 p_timed_thread_list_tail = p_node;
294                 p_timed_thread_list_head = p_node;
295         } else {
296                 /* add node to tail of non-empty list */
297                 p_timed_thread_list_tail->next = p_node;
298                 p_timed_thread_list_tail = p_node;
299         }
300
301         return 0;
302 }
303
304 /* destroy all registered timed threads */
305 void timed_thread_destroy_registered_threads(void)
306 {
307         timed_thread_node *p_node, *p_next;
308
309         for (p_node = p_timed_thread_list_head; p_node; p_node = p_next) {
310                 p_next = p_node->next;
311                 timed_thread_destroy(p_node->p_timed_thread,
312                                 p_node->addr_of_p_timed_thread);
313                 free(p_node);
314                 p_node = NULL;
315         }
316
317         p_timed_thread_list_head = NULL;
318         p_timed_thread_list_tail = NULL;
319 }