1 /**************************************************************************
3 * Copyright (C) 2009 Andreas.Fink (Andreas.Fink85@gmail.com)
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License version 2
7 * as published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 **************************************************************************/
26 struct timeval next_timeout
;
27 GHashTable
* multi_timeouts
;
30 // functions and structs for multi timeouts
33 int count_to_expiration
;
38 timeout
* parent_timeout
;
39 } multi_timeout_handler
;
43 struct timespec timeout_expires
;
44 void (*_callback
)(void*);
46 multi_timeout
* multi_timeout
;
49 void add_timeout_intern(int value_msec
, int interval_msec
, void(*_callback
)(void*), void* arg
, timeout
* t
);
50 gint
compare_timeouts(gconstpointer t1
, gconstpointer t2
);
51 gint
compare_timespecs(const struct timespec
* t1
, const struct timespec
* t2
);
52 int timespec_subtract(struct timespec
* result
, struct timespec
* x
, struct timespec
* y
);
53 struct timespec
add_msec_to_timespec(struct timespec ts
, int msec
);
56 int align_with_existing_timeouts(timeout
* t
);
57 void create_multi_timeout(timeout
* t1
, timeout
* t2
);
58 void append_multi_timeout(timeout
* t1
, timeout
* t2
);
59 int calc_multi_timeout_interval(multi_timeout_handler
* mth
);
60 void update_multi_timeout_values(multi_timeout_handler
* mth
);
61 void callback_multi_timeout(void* mth
);
62 void remove_from_multi_timeout(timeout
* t
);
63 void stop_multi_timeout(timeout
* t
);
65 void default_timeout()
71 void cleanup_timeout()
73 while (timeout_list
) {
74 timeout
* t
= timeout_list
->data
;
76 stop_multi_timeout(t
);
78 timeout_list
= g_slist_remove(timeout_list
, t
);
81 g_hash_table_destroy(multi_timeouts
);
86 /** Implementation notes for timeouts: The timeouts are kept in a GSList sorted by their
88 * That means that update_next_timeout() only have to consider the first timeout in the list,
89 * and callback_timeout_expired() only have to consider the timeouts as long as the expiration time
90 * is in the past to the current time.
91 * As time measurement we use clock_gettime(CLOCK_MONOTONIC) because this refers to a timer, which
92 * reference point lies somewhere in the past and cannot be changed, but just queried.
93 * If a single shot timer is installed it will be automatically deleted. I.e. the returned value
94 * of add_timeout will not be valid anymore. You do not need to call stop_timeout for these timeouts,
95 * however it's save to call it.
98 timeout
* add_timeout(int value_msec
, int interval_msec
, void (*_callback
)(void*), void* arg
)
100 timeout
* t
= malloc(sizeof(timeout
));
101 t
->multi_timeout
= 0;
102 add_timeout_intern(value_msec
, interval_msec
, _callback
, arg
, t
);
107 void change_timeout(timeout
*t
, int value_msec
, int interval_msec
, void(*_callback
)(), void* arg
)
109 if ( g_slist_find(timeout_list
, t
) == 0 && g_hash_table_lookup(multi_timeouts
, t
) == 0)
110 printf("programming error: timeout already deleted...");
112 if (t
->multi_timeout
)
113 remove_from_multi_timeout((timeout
*)t
);
115 timeout_list
= g_slist_remove(timeout_list
, t
);
116 add_timeout_intern(value_msec
, interval_msec
, _callback
, arg
, (timeout
*)t
);
121 void update_next_timeout()
124 timeout
* t
= timeout_list
->data
;
125 struct timespec cur_time
;
126 struct timespec next_timeout2
= { .tv_sec
=next_timeout
.tv_sec
, .tv_nsec
=next_timeout
.tv_usec
*1000 };
127 clock_gettime(CLOCK_MONOTONIC
, &cur_time
);
128 if (timespec_subtract(&next_timeout2
, &t
->timeout_expires
, &cur_time
)) {
129 next_timeout
.tv_sec
= 0;
130 next_timeout
.tv_usec
= 0;
133 next_timeout
.tv_sec
= next_timeout2
.tv_sec
;
134 next_timeout
.tv_usec
= next_timeout2
.tv_nsec
/1000;
138 next_timeout
.tv_sec
= -1;
142 void callback_timeout_expired()
144 struct timespec cur_time
;
146 while (timeout_list
) {
147 clock_gettime(CLOCK_MONOTONIC
, &cur_time
);
148 t
= timeout_list
->data
;
149 if (compare_timespecs(&t
->timeout_expires
, &cur_time
) <= 0) {
150 // it's time for the callback function
151 t
->_callback(t
->arg
);
152 if (g_slist_find(timeout_list
, t
)) {
153 // if _callback() calls stop_timeout(t) the timeout 't' was freed and is not in the timeout_list
154 timeout_list
= g_slist_remove(timeout_list
, t
);
155 if (t
->interval_msec
> 0)
156 add_timeout_intern(t
->interval_msec
, t
->interval_msec
, t
->_callback
, t
->arg
, t
);
167 void stop_timeout(timeout
* t
)
169 // if not in the list, it was deleted in callback_timeout_expired
170 if (g_slist_find(timeout_list
, t
) || g_hash_table_lookup(multi_timeouts
, t
)) {
171 if (t
->multi_timeout
)
172 remove_from_multi_timeout((timeout
*)t
);
173 timeout_list
= g_slist_remove(timeout_list
, t
);
179 void add_timeout_intern(int value_msec
, int interval_msec
, void(*_callback
)(), void* arg
, timeout
*t
)
181 t
->interval_msec
= interval_msec
;
182 t
->_callback
= _callback
;
184 struct timespec cur_time
;
185 clock_gettime(CLOCK_MONOTONIC
, &cur_time
);
186 t
->timeout_expires
= add_msec_to_timespec(cur_time
, value_msec
);
189 if (interval_msec
> 0 && !t
->multi_timeout
)
190 can_align
= align_with_existing_timeouts(t
);
192 timeout_list
= g_slist_insert_sorted(timeout_list
, t
, compare_timeouts
);
196 gint
compare_timeouts(gconstpointer t1
, gconstpointer t2
)
198 return compare_timespecs(&((timeout
*)t1
)->timeout_expires
,
199 &((timeout
*)t2
)->timeout_expires
);
203 gint
compare_timespecs(const struct timespec
* t1
, const struct timespec
* t2
)
205 if (t1
->tv_sec
< t2
->tv_sec
)
207 else if (t1
->tv_sec
== t2
->tv_sec
) {
208 if (t1
->tv_nsec
< t2
->tv_nsec
)
210 else if (t1
->tv_nsec
== t2
->tv_nsec
)
219 int timespec_subtract(struct timespec
* result
, struct timespec
* x
, struct timespec
* y
)
221 /* Perform the carry for the later subtraction by updating y. */
222 if (x
->tv_nsec
< y
->tv_nsec
) {
223 int nsec
= (y
->tv_nsec
- x
->tv_nsec
) / 1000000000 + 1;
224 y
->tv_nsec
-= 1000000000 * nsec
;
227 if (x
->tv_nsec
- y
->tv_nsec
> 1000000000) {
228 int nsec
= (x
->tv_nsec
- y
->tv_nsec
) / 1000000000;
229 y
->tv_nsec
+= 1000000000 * nsec
;
233 /* Compute the time remaining to wait. tv_nsec is certainly positive. */
234 result
->tv_sec
= x
->tv_sec
- y
->tv_sec
;
235 result
->tv_nsec
= x
->tv_nsec
- y
->tv_nsec
;
237 /* Return 1 if result is negative. */
238 return x
->tv_sec
< y
->tv_sec
;
242 struct timespec
add_msec_to_timespec(struct timespec ts
, int msec
)
244 ts
.tv_sec
+= msec
/ 1000;
245 ts
.tv_nsec
+= (msec
% 1000)*1000000;
246 if (ts
.tv_nsec
>= 1000000000) { // 10^9
248 ts
.tv_nsec
-= 1000000000;
254 int align_with_existing_timeouts(timeout
*t
)
256 GSList
* it
= timeout_list
;
258 timeout
* t2
= it
->data
;
259 if (t2
->interval_msec
> 0) {
260 if (t
->interval_msec
% t2
->interval_msec
== 0 || t2
->interval_msec
% t
->interval_msec
== 0) {
261 if (multi_timeouts
== 0)
262 multi_timeouts
= g_hash_table_new(0, 0);
263 if (!t
->multi_timeout
&& !t2
->multi_timeout
)
264 // both timeouts can be aligned, but there is no multi timeout for them
265 create_multi_timeout(t
, t2
);
267 // there is already a multi timeout, so we append the new timeout to the multi timeout
268 append_multi_timeout(t
, t2
);
278 int calc_multi_timeout_interval(multi_timeout_handler
* mth
)
280 GSList
* it
= mth
->timeout_list
;
281 timeout
* t
= it
->data
;
282 int min_interval
= t
->interval_msec
;
286 if (t
->interval_msec
< min_interval
)
287 min_interval
= t
->interval_msec
;
294 void create_multi_timeout(timeout
* t1
, timeout
* t2
)
296 multi_timeout
* mt1
= malloc(sizeof(multi_timeout
));
297 multi_timeout
* mt2
= malloc(sizeof(multi_timeout
));
298 multi_timeout_handler
* mth
= malloc(sizeof(multi_timeout_handler
));
299 timeout
* real_timeout
= malloc(sizeof(timeout
));
301 mth
->timeout_list
= 0;
302 mth
->timeout_list
= g_slist_prepend(mth
->timeout_list
, t1
);
303 mth
->timeout_list
= g_slist_prepend(mth
->timeout_list
, t2
);
304 mth
->parent_timeout
= real_timeout
;
306 g_hash_table_insert(multi_timeouts
, t1
, mth
);
307 g_hash_table_insert(multi_timeouts
, t2
, mth
);
308 g_hash_table_insert(multi_timeouts
, real_timeout
, mth
);
310 t1
->multi_timeout
= mt1
;
311 t2
->multi_timeout
= mt2
;
312 // set real_timeout->multi_timeout to something, such that we see in add_timeout_intern that
313 // it is already a multi_timeout (we never use it, except of checking for 0 ptr)
314 real_timeout
->multi_timeout
= (void*)real_timeout
;
316 timeout_list
= g_slist_remove(timeout_list
, t1
);
317 timeout_list
= g_slist_remove(timeout_list
, t2
);
319 update_multi_timeout_values(mth
);
323 void append_multi_timeout(timeout
* t1
, timeout
* t2
)
325 if (t2
->multi_timeout
) {
326 // swap t1 and t2 such that t1 is the multi timeout
332 multi_timeout
* mt
= malloc(sizeof(multi_timeout
));
333 multi_timeout_handler
* mth
= g_hash_table_lookup(multi_timeouts
, t1
);
335 mth
->timeout_list
= g_slist_prepend(mth
->timeout_list
, t2
);
336 g_hash_table_insert(multi_timeouts
, t2
, mth
);
338 t2
->multi_timeout
= mt
;
340 update_multi_timeout_values(mth
);
344 void update_multi_timeout_values(multi_timeout_handler
* mth
)
346 int interval
= calc_multi_timeout_interval(mth
);
347 int next_timeout_msec
= interval
;
349 struct timespec cur_time
;
350 clock_gettime(CLOCK_MONOTONIC
, &cur_time
);
352 GSList
* it
= mth
->timeout_list
;
353 struct timespec diff_time
;
355 timeout
* t
= it
->data
;
356 t
->multi_timeout
->count_to_expiration
= t
->interval_msec
/ interval
;
357 timespec_subtract(&diff_time
, &t
->timeout_expires
, &cur_time
);
358 int msec_to_expiration
= diff_time
.tv_sec
*1000 + diff_time
.tv_nsec
/1000000;
359 int count_left
= msec_to_expiration
/ interval
+ (msec_to_expiration%interval
!= 0);
360 t
->multi_timeout
->current_count
= t
->multi_timeout
->count_to_expiration
- count_left
;
361 if (msec_to_expiration
< next_timeout_msec
)
362 next_timeout_msec
= msec_to_expiration
;
366 mth
->parent_timeout
->interval_msec
= interval
;
367 timeout_list
= g_slist_remove(timeout_list
, mth
->parent_timeout
);
368 add_timeout_intern(next_timeout_msec
, interval
, callback_multi_timeout
, mth
, mth
->parent_timeout
);
372 void callback_multi_timeout(void* arg
)
374 multi_timeout_handler
* mth
= arg
;
375 struct timespec cur_time
;
376 clock_gettime(CLOCK_MONOTONIC
, &cur_time
);
377 GSList
* it
= mth
->timeout_list
;
379 timeout
* t
= it
->data
;
380 if (++t
->multi_timeout
->current_count
>= t
->multi_timeout
->count_to_expiration
) {
381 t
->_callback(t
->arg
);
382 t
->multi_timeout
->current_count
= 0;
383 t
->timeout_expires
= add_msec_to_timespec(cur_time
, t
->interval_msec
);
390 void remove_from_multi_timeout(timeout
* t
)
392 multi_timeout_handler
* mth
= g_hash_table_lookup(multi_timeouts
, t
);
393 g_hash_table_remove(multi_timeouts
, t
);
395 mth
->timeout_list
= g_slist_remove(mth
->timeout_list
, t
);
396 free(t
->multi_timeout
);
397 t
->multi_timeout
= 0;
399 if (g_slist_length(mth
->timeout_list
) == 1) {
400 timeout
* last_timeout
= mth
->timeout_list
->data
;
401 g_slist_remove(mth
->timeout_list
, last_timeout
);
402 free(last_timeout
->multi_timeout
);
403 last_timeout
->multi_timeout
= 0;
404 g_hash_table_remove(multi_timeouts
, last_timeout
);
405 g_hash_table_remove(multi_timeouts
, mth
->parent_timeout
);
406 mth
->parent_timeout
->multi_timeout
= 0;
407 stop_timeout(mth
->parent_timeout
);
410 struct timespec cur_time
, diff_time
;
411 clock_gettime(CLOCK_MONOTONIC
, &cur_time
);
412 timespec_subtract(&diff_time
, &t
->timeout_expires
, &cur_time
);
413 int msec_to_expiration
= diff_time
.tv_sec
*1000 + diff_time
.tv_nsec
/1000000;
414 add_timeout_intern(msec_to_expiration
, last_timeout
->interval_msec
, last_timeout
->_callback
, last_timeout
->arg
, last_timeout
);
417 update_multi_timeout_values(mth
);
421 void stop_multi_timeout(timeout
* t
)
423 multi_timeout_handler
* mth
= g_hash_table_lookup(multi_timeouts
, t
);
424 g_hash_table_remove(multi_timeouts
, mth
->parent_timeout
);
425 while (mth
->timeout_list
) {
426 timeout
* t1
= mth
->timeout_list
->data
;
427 mth
->timeout_list
= g_slist_remove(mth
->timeout_list
, t1
);
428 g_hash_table_remove(multi_timeouts
, t1
);
429 free(t1
->multi_timeout
);