1 /*
2 * Copyright (c) 2010, Sangoma Technologies
3 * Moises Silva <moy@sangoma.com>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * * 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 * * Neither the name of the original author; nor the names of any contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
26 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 #include "private/ftdm_core.h"
36
37 #ifdef __WINDOWS__
38 struct ftdm_timezone {
39 int tz_minuteswest; /* minutes W of Greenwich */
40 int tz_dsttime; /* type of dst correction */
41 };
gettimeofday(struct timeval * tv,struct ftdm_timezone * tz)42 int gettimeofday(struct timeval *tv, struct ftdm_timezone *tz)
43 {
44 FILETIME ft;
45 unsigned __int64 tmpres = 0;
46 static int tzflag;
47 if (NULL != tv) {
48 GetSystemTimeAsFileTime(&ft);
49 tmpres |= ft.dwHighDateTime;
50 tmpres <<= 32;
51 tmpres |= ft.dwLowDateTime;
52
53 /*converting file time to unix epoch */
54 tmpres /= 10; /*convert into microseconds */
55 tmpres -= DELTA_EPOCH_IN_MICROSECS;
56 tv->tv_sec = (long) (tmpres / 1000000UL);
57 tv->tv_usec = (long) (tmpres % 1000000UL);
58 }
59 if (NULL != tz) {
60 if (!tzflag) {
61 _tzset();
62 tzflag++;
63 }
64 tz->tz_minuteswest = _timezone / 60;
65 tz->tz_dsttime = _daylight;
66 }
67 return 0;
68 }
69 #endif /* __WINDOWS__ */
70
71 typedef struct ftdm_timer ftdm_timer_t;
72
73 static struct {
74 ftdm_sched_t *freeruns;
75 ftdm_mutex_t *mutex;
76 ftdm_bool_t running;
77 } sched_globals;
78
79 struct ftdm_sched {
80 char name[80];
81 ftdm_timer_id_t currid;
82 ftdm_mutex_t *mutex;
83 ftdm_timer_t *timers;
84 int freerun;
85 ftdm_sched_t *next;
86 ftdm_sched_t *prev;
87 };
88
89 struct ftdm_timer {
90 char name[80];
91 ftdm_timer_id_t id;
92 struct timeval time;
93 void *usrdata;
94 ftdm_sched_callback_t callback;
95 ftdm_timer_t *next;
96 ftdm_timer_t *prev;
97 };
98
99 /* FIXME: use ftdm_interrupt_t to wait for new schedules to monitor */
100 #define SCHED_MAX_SLEEP 100
run_main_schedule(ftdm_thread_t * thread,void * data)101 static void *run_main_schedule(ftdm_thread_t *thread, void *data)
102 {
103 int32_t timeto;
104 int32_t sleepms;
105 ftdm_status_t status;
106 ftdm_sched_t *current = NULL;
107
108 ftdm_unused_arg(data);
109 ftdm_unused_arg(thread);
110
111 while (ftdm_running()) {
112
113 sleepms = SCHED_MAX_SLEEP;
114
115 ftdm_mutex_lock(sched_globals.mutex);
116
117 if (!sched_globals.freeruns) {
118
119 /* there are no free runs, wait a bit and check again (FIXME: use ftdm_interrupt_t for this) */
120 ftdm_mutex_unlock(sched_globals.mutex);
121
122 if (ftdm_running()) {
123 ftdm_sleep(sleepms);
124 }
125 }
126
127 for (current = sched_globals.freeruns; current; current = current->next) {
128 if (!ftdm_running()) {
129 break;
130 }
131
132 /* first run the schedule */
133 ftdm_sched_run(current);
134
135 /* now find out how much time to sleep until running them again */
136 status = ftdm_sched_get_time_to_next_timer(current, &timeto);
137 if (status != FTDM_SUCCESS) {
138 ftdm_log(FTDM_LOG_WARNING, "Failed to get time to next timer for schedule %s, skipping\n", current->name);
139 continue;
140 }
141
142 /* if timeto == -1 we don't want to sleep forever, so keep the last sleepms */
143 if (timeto != -1 && sleepms > timeto) {
144 sleepms = timeto;
145 }
146 }
147
148 ftdm_mutex_unlock(sched_globals.mutex);
149
150 if (ftdm_running()) {
151 ftdm_sleep(sleepms);
152 }
153 }
154 ftdm_log(FTDM_LOG_NOTICE, "Main scheduling thread going out ...\n");
155 sched_globals.running = 0;
156 return NULL;
157 }
158
ftdm_sched_global_init()159 FT_DECLARE(ftdm_status_t) ftdm_sched_global_init()
160 {
161 ftdm_log(FTDM_LOG_DEBUG, "Initializing scheduling API\n");
162 memset(&sched_globals, 0, sizeof(sched_globals));
163 if (ftdm_mutex_create(&sched_globals.mutex) == FTDM_SUCCESS) {
164 return FTDM_SUCCESS;
165 }
166 return FTDM_FAIL;
167 }
168
ftdm_sched_global_destroy()169 FT_DECLARE(ftdm_status_t) ftdm_sched_global_destroy()
170 {
171 ftdm_mutex_destroy(&sched_globals.mutex);
172 memset(&sched_globals, 0, sizeof(sched_globals));
173 return FTDM_SUCCESS;
174 }
175
ftdm_sched_free_run(ftdm_sched_t * sched)176 FT_DECLARE(ftdm_status_t) ftdm_sched_free_run(ftdm_sched_t *sched)
177 {
178 ftdm_status_t status = FTDM_FAIL;
179 ftdm_assert_return(sched != NULL, FTDM_EINVAL, "invalid pointer\n");
180
181 ftdm_mutex_lock(sched->mutex);
182
183 ftdm_mutex_lock(sched_globals.mutex);
184
185 if (sched->freerun) {
186 ftdm_log(FTDM_LOG_ERROR, "Schedule %s is already running in free run\n", sched->name);
187 goto done;
188 }
189 sched->freerun = 1;
190
191 if (sched_globals.running == FTDM_FALSE) {
192 ftdm_log(FTDM_LOG_NOTICE, "Launching main schedule thread\n");
193 status = ftdm_thread_create_detached(run_main_schedule, NULL);
194 if (status != FTDM_SUCCESS) {
195 ftdm_log(FTDM_LOG_CRIT, "Failed to launch main schedule thread\n");
196 goto done;
197 }
198 sched_globals.running = FTDM_TRUE;
199 }
200
201 ftdm_log(FTDM_LOG_DEBUG, "Running schedule %s in the main schedule thread\n", sched->name);
202 status = FTDM_SUCCESS;
203
204 /* Add the schedule to the global list of free runs */
205 if (!sched_globals.freeruns) {
206 sched_globals.freeruns = sched;
207 } else {
208 sched->next = sched_globals.freeruns;
209 sched_globals.freeruns->prev = sched;
210 sched_globals.freeruns = sched;
211 }
212
213 done:
214 ftdm_mutex_unlock(sched_globals.mutex);
215
216 ftdm_mutex_unlock(sched->mutex);
217 return status;
218 }
219
ftdm_free_sched_running(void)220 FT_DECLARE(ftdm_bool_t) ftdm_free_sched_running(void)
221 {
222 return sched_globals.running;
223 }
224
ftdm_free_sched_stop(void)225 FT_DECLARE(ftdm_bool_t) ftdm_free_sched_stop(void)
226 {
227 /* currently we really dont stop the thread here, we rely on freetdm being shutdown and ftdm_running() to be false
228 * so the scheduling thread dies and we just wait for it here */
229 uint32_t sanity = 100;
230 while (ftdm_free_sched_running() && --sanity) {
231 ftdm_log(FTDM_LOG_DEBUG, "Waiting for main schedule thread to finish\n");
232 ftdm_sleep(100);
233 }
234
235 if (!sanity) {
236 ftdm_log(FTDM_LOG_CRIT, "schedule thread did not stop running, we may crash on shutdown\n");
237 return FTDM_FALSE;
238 }
239
240 return FTDM_TRUE;
241 }
242
ftdm_sched_create(ftdm_sched_t ** sched,const char * name)243 FT_DECLARE(ftdm_status_t) ftdm_sched_create(ftdm_sched_t **sched, const char *name)
244 {
245 ftdm_sched_t *newsched = NULL;
246
247 ftdm_assert_return(sched != NULL, FTDM_EINVAL, "invalid pointer\n");
248 ftdm_assert_return(name != NULL, FTDM_EINVAL, "invalid sched name\n");
249
250 *sched = NULL;
251
252 newsched = ftdm_calloc(1, sizeof(*newsched));
253 if (!newsched) {
254 return FTDM_MEMERR;
255 }
256
257 if (ftdm_mutex_create(&newsched->mutex) != FTDM_SUCCESS) {
258 goto failed;
259 }
260
261 ftdm_set_string(newsched->name, name);
262 newsched->currid = 1;
263
264 *sched = newsched;
265 ftdm_log(FTDM_LOG_DEBUG, "Created schedule %s\n", name);
266 return FTDM_SUCCESS;
267
268 failed:
269 ftdm_log(FTDM_LOG_CRIT, "Failed to create schedule\n");
270
271 if (newsched) {
272 if (newsched->mutex) {
273 ftdm_mutex_destroy(&newsched->mutex);
274 }
275 ftdm_safe_free(newsched);
276 }
277 return FTDM_FAIL;
278 }
279
ftdm_sched_run(ftdm_sched_t * sched)280 FT_DECLARE(ftdm_status_t) ftdm_sched_run(ftdm_sched_t *sched)
281 {
282 ftdm_status_t status = FTDM_FAIL;
283 ftdm_timer_t *runtimer;
284 ftdm_timer_t *timer;
285 ftdm_sched_callback_t callback;
286 int ms = 0;
287 int rc = -1;
288 void *data;
289 struct timeval now;
290
291 ftdm_assert_return(sched != NULL, FTDM_EINVAL, "sched is null!\n");
292
293 tryagain:
294
295 ftdm_mutex_lock(sched->mutex);
296
297 rc = gettimeofday(&now, NULL);
298 if (rc == -1) {
299 ftdm_log(FTDM_LOG_ERROR, "Failed to retrieve time of day\n");
300 goto done;
301 }
302
303 timer = sched->timers;
304 while (timer) {
305 runtimer = timer;
306 timer = runtimer->next;
307
308 ms = ((runtimer->time.tv_sec - now.tv_sec) * 1000) +
309 ((runtimer->time.tv_usec - now.tv_usec) / 1000);
310
311 if (ms <= 0) {
312
313 if (runtimer == sched->timers) {
314 sched->timers = runtimer->next;
315 if (sched->timers) {
316 sched->timers->prev = NULL;
317 }
318 }
319
320 callback = runtimer->callback;
321 data = runtimer->usrdata;
322 if (runtimer->next) {
323 runtimer->next->prev = runtimer->prev;
324 }
325 if (runtimer->prev) {
326 runtimer->prev->next = runtimer->next;
327 }
328
329 runtimer->id = 0;
330 ftdm_safe_free(runtimer);
331
332 /* avoid deadlocks by releasing the sched lock before triggering callbacks */
333 ftdm_mutex_unlock(sched->mutex);
334
335 callback(data);
336 /* after calling a callback we must start the scanning again since the
337 * callback or some other thread may have added or cancelled timers to
338 * the linked list */
339 goto tryagain;
340 }
341 }
342
343 status = FTDM_SUCCESS;
344 done:
345 ftdm_mutex_unlock(sched->mutex);
346 ftdm_unused_arg(sched);
347 return status;
348 }
349
ftdm_sched_timer(ftdm_sched_t * sched,const char * name,int ms,ftdm_sched_callback_t callback,void * data,ftdm_timer_id_t * timerid)350 FT_DECLARE(ftdm_status_t) ftdm_sched_timer(ftdm_sched_t *sched, const char *name,
351 int ms, ftdm_sched_callback_t callback, void *data, ftdm_timer_id_t *timerid)
352 {
353 ftdm_status_t status = FTDM_FAIL;
354 struct timeval now;
355 int rc = 0;
356 ftdm_timer_t *newtimer;
357
358 ftdm_assert_return(sched != NULL, FTDM_EINVAL, "sched is null!\n");
359 ftdm_assert_return(name != NULL, FTDM_EINVAL, "timer name is null!\n");
360 ftdm_assert_return(callback != NULL, FTDM_EINVAL, "sched callback is null!\n");
361 ftdm_assert_return(ms > 0, FTDM_EINVAL, "milliseconds must be bigger than 0!\n");
362
363 if (timerid) {
364 *timerid = 0;
365 }
366
367 rc = gettimeofday(&now, NULL);
368 if (rc == -1) {
369 ftdm_log(FTDM_LOG_ERROR, "Failed to retrieve time of day\n");
370 return FTDM_FAIL;
371 }
372
373 ftdm_mutex_lock(sched->mutex);
374
375 newtimer = ftdm_calloc(1, sizeof(*newtimer));
376 if (!newtimer) {
377 goto done;
378 }
379 newtimer->id = sched->currid;
380 sched->currid++;
381 if (!sched->currid) {
382 ftdm_log(FTDM_LOG_NOTICE, "Timer id wrap around for sched %s\n", sched->name);
383 /* we do not want currid to be zero since is an invalid id
384 * TODO: check that currid does not exists already in the context, it'd be insane
385 * though, having a timer to live all that time */
386 sched->currid++;
387 }
388
389 ftdm_set_string(newtimer->name, name);
390 newtimer->callback = callback;
391 newtimer->usrdata = data;
392
393 newtimer->time.tv_sec = now.tv_sec + (ms / 1000);
394 newtimer->time.tv_usec = now.tv_usec + (ms % 1000) * 1000;
395 if (newtimer->time.tv_usec >= FTDM_MICROSECONDS_PER_SECOND) {
396 newtimer->time.tv_sec += 1;
397 newtimer->time.tv_usec -= FTDM_MICROSECONDS_PER_SECOND;
398 }
399
400 if (!sched->timers) {
401 sched->timers = newtimer;
402 } else {
403 newtimer->next = sched->timers;
404 sched->timers->prev = newtimer;
405 sched->timers = newtimer;
406 }
407
408 if (timerid) {
409 *timerid = newtimer->id;
410 }
411
412 status = FTDM_SUCCESS;
413 done:
414 ftdm_mutex_unlock(sched->mutex);
415 ftdm_unused_arg(sched);
416 ftdm_unused_arg(name);
417 ftdm_unused_arg(ms);
418 ftdm_unused_arg(callback);
419 ftdm_unused_arg(data);
420 ftdm_unused_arg(timerid);
421 return status;
422 }
423
ftdm_sched_get_time_to_next_timer(const ftdm_sched_t * sched,int32_t * timeto)424 FT_DECLARE(ftdm_status_t) ftdm_sched_get_time_to_next_timer(const ftdm_sched_t *sched, int32_t *timeto)
425 {
426 ftdm_status_t status = FTDM_FAIL;
427 int res = -1;
428 int ms = 0;
429 struct timeval currtime;
430 ftdm_timer_t *current = NULL;
431 ftdm_timer_t *winner = NULL;
432
433 /* forever by default */
434 *timeto = -1;
435
436 ftdm_mutex_lock(sched->mutex);
437
438 res = gettimeofday(&currtime, NULL);
439 if (-1 == res) {
440 ftdm_log(FTDM_LOG_ERROR, "Failed to get next event time\n");
441 goto done;
442 }
443 status = FTDM_SUCCESS;
444 current = sched->timers;
445 while (current) {
446 /* if no winner, set this as winner */
447 if (!winner) {
448 winner = current;
449 }
450 current = current->next;
451 /* if no more timers, return the winner */
452 if (!current) {
453 ms = (((winner->time.tv_sec - currtime.tv_sec) * 1000) +
454 ((winner->time.tv_usec - currtime.tv_usec) / 1000));
455
456 /* if the timer is expired already, return 0 to attend immediately */
457 if (ms < 0) {
458 *timeto = 0;
459 break;
460 }
461 *timeto = ms;
462 break;
463 }
464
465 /* if the winner timer is after the current timer, then we have a new winner */
466 if (winner->time.tv_sec > current->time.tv_sec
467 || (winner->time.tv_sec == current->time.tv_sec &&
468 winner->time.tv_usec > current->time.tv_usec)) {
469 winner = current;
470 }
471 }
472
473 done:
474 ftdm_mutex_unlock(sched->mutex);
475 ftdm_unused_arg(timeto);
476 ftdm_unused_arg(sched);
477 return status;
478 }
479
ftdm_sched_cancel_timer(ftdm_sched_t * sched,ftdm_timer_id_t timerid)480 FT_DECLARE(ftdm_status_t) ftdm_sched_cancel_timer(ftdm_sched_t *sched, ftdm_timer_id_t timerid)
481 {
482 ftdm_status_t status = FTDM_FAIL;
483 ftdm_timer_t *timer;
484
485 ftdm_assert_return(sched != NULL, FTDM_EINVAL, "sched is null!\n");
486
487 if (!timerid) {
488 return FTDM_SUCCESS;
489 }
490
491 ftdm_mutex_lock(sched->mutex);
492
493 /* look for the timer and destroy it */
494 for (timer = sched->timers; timer; timer = timer->next) {
495 if (timer->id == timerid) {
496 if (timer == sched->timers) {
497 /* it's the head timer, put a new head */
498 sched->timers = timer->next;
499 }
500 if (timer->prev) {
501 timer->prev->next = timer->next;
502 }
503 if (timer->next) {
504 timer->next->prev = timer->prev;
505 }
506 ftdm_safe_free(timer);
507 status = FTDM_SUCCESS;
508 break;
509 }
510 }
511
512 ftdm_mutex_unlock(sched->mutex);
513
514 return status;
515 }
516
ftdm_sched_destroy(ftdm_sched_t ** insched)517 FT_DECLARE(ftdm_status_t) ftdm_sched_destroy(ftdm_sched_t **insched)
518 {
519 ftdm_sched_t *sched = NULL;
520 ftdm_timer_t *timer;
521 ftdm_timer_t *deltimer;
522 ftdm_assert_return(insched != NULL, FTDM_EINVAL, "sched is null!\n");
523 ftdm_assert_return(*insched != NULL, FTDM_EINVAL, "sched is null!\n");
524
525 sched = *insched;
526
527 /* since destroying a sched may affect the global list, we gotta check */
528 ftdm_mutex_lock(sched_globals.mutex);
529
530 /* if we're head, replace head with our next (whatever our next is, even null will do) */
531 if (sched == sched_globals.freeruns) {
532 sched_globals.freeruns = sched->next;
533 }
534 /* if we have a previous member (then we were not head) set our previous next to our next */
535 if (sched->prev) {
536 sched->prev->next = sched->next;
537 }
538 /* if we have a next then set their prev to our prev (if we were head prev will be null and sched->next is already the new head) */
539 if (sched->next) {
540 sched->next->prev = sched->prev;
541 }
542
543 ftdm_mutex_unlock(sched_globals.mutex);
544
545 /* now grab the sched mutex */
546 ftdm_mutex_lock(sched->mutex);
547
548 timer = sched->timers;
549 while (timer) {
550 deltimer = timer;
551 timer = timer->next;
552 ftdm_safe_free(deltimer);
553 }
554
555 ftdm_log(FTDM_LOG_DEBUG, "Destroying schedule %s\n", sched->name);
556
557 ftdm_mutex_unlock(sched->mutex);
558
559 ftdm_mutex_destroy(&sched->mutex);
560
561 ftdm_safe_free(sched);
562
563 *insched = NULL;
564 return FTDM_SUCCESS;
565 }
566
567 /* For Emacs:
568 * Local Variables:
569 * mode:c
570 * indent-tabs-mode:t
571 * tab-width:4
572 * c-basic-offset:4
573 * End:
574 * For VIM:
575 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet:
576 */
577