root/src/ftdm_sched.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. run_main_schedule
  2. FT_DECLARE
  3. FT_DECLARE
  4. FT_DECLARE
  5. FT_DECLARE
  6. FT_DECLARE
  7. FT_DECLARE
  8. FT_DECLARE
  9. FT_DECLARE
  10. FT_DECLARE
  11. FT_DECLARE

   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 typedef struct ftdm_timer ftdm_timer_t;
  38 
  39 static struct {
  40         ftdm_sched_t *freeruns;
  41         ftdm_mutex_t *mutex;
  42         ftdm_bool_t running;
  43 } sched_globals;
  44 
  45 struct ftdm_sched {
  46         char name[80];
  47         ftdm_timer_id_t currid;
  48         ftdm_mutex_t *mutex;
  49         ftdm_timer_t *timers;
  50         int freerun;
  51         ftdm_sched_t *next;
  52         ftdm_sched_t *prev;
  53 };
  54 
  55 struct ftdm_timer {
  56         char name[80];
  57         ftdm_timer_id_t id;
  58 #ifdef __linux__
  59         struct timeval time;
  60 #endif
  61         void *usrdata;
  62         ftdm_sched_callback_t callback;
  63         ftdm_timer_t *next;
  64         ftdm_timer_t *prev;
  65 };
  66 
  67 /* FIXME: use ftdm_interrupt_t to wait for new schedules to monitor */
  68 #define SCHED_MAX_SLEEP 100
  69 static void *run_main_schedule(ftdm_thread_t *thread, void *data)
  70 {
  71         int32_t timeto;
  72         int32_t sleepms;
  73         ftdm_status_t status;
  74         ftdm_sched_t *current = NULL;
  75 #ifdef __WINDOWS__
  76         UNREFERENCED_PARAMETER(data);
  77         UNREFERENCED_PARAMETER(thread);
  78 #endif
  79         while (ftdm_running()) {
  80                 
  81                 sleepms = SCHED_MAX_SLEEP;
  82 
  83                 ftdm_mutex_lock(sched_globals.mutex);
  84 
  85                 if (!sched_globals.freeruns) {
  86                 
  87                         /* there are no free runs, wait a bit and check again (FIXME: use ftdm_interrupt_t for this) */
  88                         ftdm_mutex_unlock(sched_globals.mutex);
  89 
  90                         ftdm_sleep(sleepms);
  91                 }
  92 
  93                 for (current = sched_globals.freeruns; current; current = current->next) {
  94 
  95                         /* first run the schedule */
  96                         ftdm_sched_run(current);
  97 
  98                         /* now find out how much time to sleep until running them again */
  99                         status = ftdm_sched_get_time_to_next_timer(current, &timeto);
 100                         if (status != FTDM_SUCCESS) {
 101                                 ftdm_log(FTDM_LOG_WARNING, "Failed to get time to next timer for schedule %s, skipping\n", current->name);      
 102                                 continue;
 103                         }
 104 
 105                         /* if timeto == -1 we don't want to sleep forever, so keep the last sleepms */
 106                         if (timeto != -1 && sleepms > timeto) {
 107                                 sleepms = timeto;
 108                         }
 109                 }
 110 
 111                 ftdm_mutex_unlock(sched_globals.mutex);
 112 
 113                 ftdm_sleep(sleepms);
 114         }
 115         ftdm_log(FTDM_LOG_NOTICE, "Main scheduling thread going out ...\n");
 116         sched_globals.running = 0;
 117         return NULL;
 118 }
 119 
 120 FT_DECLARE(ftdm_status_t) ftdm_sched_global_init()
 121 {
 122         ftdm_log(FTDM_LOG_DEBUG, "Initializing scheduling API\n");
 123         memset(&sched_globals, 0, sizeof(sched_globals));
 124         if (ftdm_mutex_create(&sched_globals.mutex) == FTDM_SUCCESS) {
 125                 return FTDM_SUCCESS;
 126         }
 127         return FTDM_FAIL;
 128 }
 129 
 130 FT_DECLARE(ftdm_status_t) ftdm_sched_free_run(ftdm_sched_t *sched)
 131 {
 132         ftdm_status_t status = FTDM_FAIL;
 133         ftdm_assert_return(sched != NULL, FTDM_EINVAL, "invalid pointer\n");
 134 
 135         ftdm_mutex_lock(sched->mutex);
 136 
 137         ftdm_mutex_lock(sched_globals.mutex);
 138 
 139         if (sched->freerun) {
 140                 ftdm_log(FTDM_LOG_ERROR, "Schedule %s is already running in free run\n", sched->name);
 141                 goto done;
 142         }
 143         sched->freerun = 1;
 144 
 145         if (sched_globals.running == FTDM_FALSE) {
 146                 ftdm_log(FTDM_LOG_NOTICE, "Launching main schedule thread\n");
 147                 status = ftdm_thread_create_detached(run_main_schedule, NULL);
 148                 if (status != FTDM_SUCCESS) {
 149                         ftdm_log(FTDM_LOG_CRIT, "Failed to launch main schedule thread\n");
 150                         goto done;
 151                 } 
 152                 sched_globals.running = FTDM_TRUE;
 153         }
 154 
 155         ftdm_log(FTDM_LOG_DEBUG, "Running schedule %s in the main schedule thread\n", sched->name);
 156         status = FTDM_SUCCESS;
 157         
 158         /* Add the schedule to the global list of free runs */
 159         if (!sched_globals.freeruns) {
 160                 sched_globals.freeruns = sched;
 161         }  else {
 162                 sched->next = sched_globals.freeruns;
 163                 sched_globals.freeruns->prev = sched;
 164                 sched_globals.freeruns = sched;
 165         }
 166 
 167 done:
 168         ftdm_mutex_unlock(sched_globals.mutex);
 169 
 170         ftdm_mutex_unlock(sched->mutex);
 171         return status;
 172 }
 173 
 174 FT_DECLARE(ftdm_bool_t) ftdm_free_sched_running(void)
 175 {
 176         return sched_globals.running;
 177 }
 178 
 179 FT_DECLARE(ftdm_bool_t) ftdm_free_sched_stop(void)
 180 {
 181         /* currently we really dont stop the thread here, we rely on freetdm being shutdown and ftdm_running() to be false 
 182          * so the scheduling thread dies and we just wait for it here */
 183         uint32_t sanity = 100;
 184         while (ftdm_free_sched_running() && --sanity) {
 185                 ftdm_log(FTDM_LOG_DEBUG, "Waiting for main schedule thread to finish\n");
 186                 ftdm_sleep(100);
 187         }
 188 
 189         if (!sanity) {
 190                 ftdm_log(FTDM_LOG_CRIT, "schedule thread did not stop running, we may crash on shutdown\n");
 191                 return FTDM_FALSE;
 192         }
 193 
 194         return FTDM_TRUE;
 195 }
 196 
 197 FT_DECLARE(ftdm_status_t) ftdm_sched_create(ftdm_sched_t **sched, const char *name)
 198 {
 199         ftdm_sched_t *newsched = NULL;
 200 
 201         ftdm_assert_return(sched != NULL, FTDM_EINVAL, "invalid pointer\n");
 202         ftdm_assert_return(name != NULL, FTDM_EINVAL, "invalid sched name\n");
 203 
 204         *sched = NULL;
 205 
 206         newsched = ftdm_calloc(1, sizeof(*newsched));
 207         if (!newsched) {
 208                 return FTDM_MEMERR;
 209         }
 210 
 211         if (ftdm_mutex_create(&newsched->mutex) != FTDM_SUCCESS) {
 212                 goto failed;
 213         }
 214 
 215         ftdm_set_string(newsched->name, name);
 216         newsched->currid = 1;
 217 
 218         *sched = newsched;
 219         ftdm_log(FTDM_LOG_DEBUG, "Created schedule %s\n", name);
 220         return FTDM_SUCCESS;
 221 
 222 failed:
 223         ftdm_log(FTDM_LOG_CRIT, "Failed to create schedule\n");
 224 
 225         if (newsched) {
 226                 if (newsched->mutex) {
 227                         ftdm_mutex_destroy(&newsched->mutex);
 228                 }
 229                 ftdm_safe_free(newsched);
 230         }
 231         return FTDM_FAIL;
 232 }
 233 
 234 FT_DECLARE(ftdm_status_t) ftdm_sched_run(ftdm_sched_t *sched)
 235 {
 236         ftdm_status_t status = FTDM_FAIL;
 237 #ifdef __linux__
 238         ftdm_timer_t *runtimer;
 239         ftdm_timer_t *timer;
 240         ftdm_sched_callback_t callback;
 241         int ms = 0;
 242         int rc = -1;
 243         void *data;
 244         struct timeval now;
 245 
 246         ftdm_assert_return(sched != NULL, FTDM_EINVAL, "sched is null!\n");
 247 
 248 tryagain:
 249 
 250         ftdm_mutex_lock(sched->mutex);
 251 
 252         rc = gettimeofday(&now, NULL);
 253         if (rc == -1) {
 254                 ftdm_log(FTDM_LOG_ERROR, "Failed to retrieve time of day\n");
 255                 goto done;
 256         }
 257 
 258         timer = sched->timers;
 259         while (timer) {
 260                 runtimer = timer;
 261                 timer = runtimer->next;
 262 
 263                 ms = ((runtimer->time.tv_sec - now.tv_sec) * 1000) +
 264                      ((runtimer->time.tv_usec - now.tv_usec) / 1000);
 265 
 266                 if (ms <= 0) {
 267 
 268                         if (runtimer == sched->timers) {
 269                                 sched->timers = runtimer->next;
 270                                 if (sched->timers) {
 271                                         sched->timers->prev = NULL;
 272                                 }
 273                         }
 274 
 275                         callback = runtimer->callback;
 276                         data = runtimer->usrdata;
 277                         if (runtimer->next) {
 278                                 runtimer->next->prev = runtimer->prev;
 279                         }
 280                         if (runtimer->prev) {
 281                                 runtimer->prev->next = runtimer->next;
 282                         }
 283 
 284                         runtimer->id = 0;
 285                         ftdm_safe_free(runtimer);
 286 
 287                         /* avoid deadlocks by releasing the sched lock before triggering callbacks */
 288                         ftdm_mutex_unlock(sched->mutex);
 289 
 290                         callback(data);
 291                         /* after calling a callback we must start the scanning again since the
 292                          * callback or some other thread may have added or cancelled timers to 
 293                          * the linked list */
 294                         goto tryagain;
 295                 }
 296         }
 297 
 298         status = FTDM_SUCCESS;
 299 
 300 done:
 301 
 302         ftdm_mutex_unlock(sched->mutex);
 303 #else
 304         ftdm_log(FTDM_LOG_CRIT, "Not implemented in this platform\n");
 305         status = FTDM_NOTIMPL;
 306 #endif
 307 #ifdef __WINDOWS__
 308         UNREFERENCED_PARAMETER(sched);
 309 #endif
 310 
 311         return status;
 312 }
 313 
 314 FT_DECLARE(ftdm_status_t) ftdm_sched_timer(ftdm_sched_t *sched, const char *name, 
 315                 int ms, ftdm_sched_callback_t callback, void *data, ftdm_timer_id_t *timerid)
 316 {
 317         ftdm_status_t status = FTDM_FAIL;
 318 #ifdef __linux__
 319         struct timeval now;
 320         int rc = 0;
 321         ftdm_timer_t *newtimer;
 322 
 323         ftdm_assert_return(sched != NULL, FTDM_EINVAL, "sched is null!\n");
 324         ftdm_assert_return(name != NULL, FTDM_EINVAL, "timer name is null!\n");
 325         ftdm_assert_return(callback != NULL, FTDM_EINVAL, "sched callback is null!\n");
 326         ftdm_assert_return(ms > 0, FTDM_EINVAL, "milliseconds must be bigger than 0!\n");
 327 
 328         if (timerid) {
 329                 *timerid = 0;
 330         }
 331 
 332         rc = gettimeofday(&now, NULL);
 333         if (rc == -1) {
 334                 ftdm_log(FTDM_LOG_ERROR, "Failed to retrieve time of day\n");
 335                 return FTDM_FAIL;
 336         }
 337 
 338         ftdm_mutex_lock(sched->mutex);
 339 
 340         newtimer = ftdm_calloc(1, sizeof(*newtimer));
 341         if (!newtimer) {
 342                 goto done;
 343         }
 344         newtimer->id = sched->currid;
 345         sched->currid++;
 346         if (!sched->currid) {
 347                 ftdm_log(FTDM_LOG_NOTICE, "Timer id wrap around for sched %s\n", sched->name);
 348                 /* we do not want currid to be zero since is an invalid id 
 349                  * TODO: check that currid does not exists already in the context, it'd be insane
 350                  * though, having a timer to live all that time */
 351                 sched->currid++;
 352         }
 353 
 354         ftdm_set_string(newtimer->name, name);
 355         newtimer->callback = callback;
 356         newtimer->usrdata = data;
 357 
 358         newtimer->time.tv_sec = now.tv_sec + (ms / 1000);
 359         newtimer->time.tv_usec = now.tv_usec + (ms % 1000) * 1000;
 360         if (newtimer->time.tv_usec >= FTDM_MICROSECONDS_PER_SECOND) {
 361                 newtimer->time.tv_sec += 1;
 362                 newtimer->time.tv_usec -= FTDM_MICROSECONDS_PER_SECOND;
 363         }
 364 
 365         if (!sched->timers) {
 366                 sched->timers = newtimer;
 367         }  else {
 368                 newtimer->next = sched->timers;
 369                 sched->timers->prev = newtimer;
 370                 sched->timers = newtimer;
 371         }
 372 
 373         if (timerid) {
 374                 *timerid = newtimer->id;
 375         }
 376 
 377         status = FTDM_SUCCESS;
 378 done:
 379 
 380         ftdm_mutex_unlock(sched->mutex);
 381 #else
 382         ftdm_log(FTDM_LOG_CRIT, "Not implemented in this platform\n");
 383         status = FTDM_NOTIMPL;
 384 #endif
 385 #ifdef __WINDOWS__
 386         UNREFERENCED_PARAMETER(sched);
 387         UNREFERENCED_PARAMETER(name);
 388         UNREFERENCED_PARAMETER(ms);
 389         UNREFERENCED_PARAMETER(callback);
 390         UNREFERENCED_PARAMETER(data);
 391         UNREFERENCED_PARAMETER(timerid);
 392 #endif
 393         return status;
 394 }
 395 
 396 FT_DECLARE(ftdm_status_t) ftdm_sched_get_time_to_next_timer(const ftdm_sched_t *sched, int32_t *timeto)
 397 {
 398         ftdm_status_t status = FTDM_FAIL;
 399 #ifdef __linux__
 400         int res = -1;
 401         int ms = 0;
 402         struct timeval currtime;
 403         ftdm_timer_t *current = NULL;
 404         ftdm_timer_t *winner = NULL;
 405         
 406         /* forever by default */
 407         *timeto = -1;
 408 
 409         ftdm_mutex_lock(sched->mutex);
 410 
 411         res = gettimeofday(&currtime, NULL);
 412         if (-1 == res) {
 413                 ftdm_log(FTDM_LOG_ERROR, "Failed to get next event time\n");
 414                 goto done;
 415         }
 416         status = FTDM_SUCCESS;
 417         current = sched->timers;
 418         while (current) {
 419                 /* if no winner, set this as winner */
 420                 if (!winner) {
 421                         winner = current;
 422                 }
 423                 current = current->next;
 424                 /* if no more timers, return the winner */
 425                 if (!current) {
 426                         ms = (((winner->time.tv_sec - currtime.tv_sec) * 1000) + 
 427                              ((winner->time.tv_usec - currtime.tv_usec) / 1000));
 428 
 429                         /* if the timer is expired already, return 0 to attend immediately */
 430                         if (ms < 0) {
 431                                 *timeto = 0;
 432                                 break;
 433                         }
 434                         *timeto = ms;
 435                         break;
 436                 }
 437 
 438                 /* if the winner timer is after the current timer, then we have a new winner */
 439                 if (winner->time.tv_sec > current->time.tv_sec
 440                     || (winner->time.tv_sec == current->time.tv_sec &&
 441                        winner->time.tv_usec > current->time.tv_usec)) {
 442                         winner = current;
 443                 }
 444         }
 445 
 446 done:
 447         ftdm_mutex_unlock(sched->mutex);
 448 #else
 449         ftdm_log(FTDM_LOG_ERROR, "Implement me!\n");
 450         status = FTDM_NOTIMPL;
 451 #endif
 452 #ifdef __WINDOWS__
 453         UNREFERENCED_PARAMETER(timeto);
 454         UNREFERENCED_PARAMETER(sched);
 455 #endif
 456 
 457         return status;
 458 }
 459 
 460 FT_DECLARE(ftdm_status_t) ftdm_sched_cancel_timer(ftdm_sched_t *sched, ftdm_timer_id_t timerid)
 461 {
 462         ftdm_status_t status = FTDM_FAIL;
 463         ftdm_timer_t *timer;
 464 
 465         ftdm_assert_return(sched != NULL, FTDM_EINVAL, "sched is null!\n");
 466 
 467         if (!timerid) {
 468                 return FTDM_SUCCESS;
 469         }
 470 
 471         ftdm_mutex_lock(sched->mutex);
 472 
 473         /* look for the timer and destroy it */
 474         for (timer = sched->timers; timer; timer = timer->next) {
 475                 if (timer->id == timerid) {
 476                         if (timer == sched->timers) {
 477                                 /* it's the head timer, put a new head */
 478                                 sched->timers = timer->next;
 479                         }
 480                         if (timer->prev) {
 481                                 timer->prev->next = timer->next;
 482                         }
 483                         if (timer->next) {
 484                                 timer->next->prev = timer->prev;
 485                         }
 486                         ftdm_safe_free(timer);
 487                         status = FTDM_SUCCESS;
 488                         break;
 489                 }
 490         }
 491 
 492         ftdm_mutex_unlock(sched->mutex);
 493 
 494         return status;
 495 }
 496 
 497 FT_DECLARE(ftdm_status_t) ftdm_sched_destroy(ftdm_sched_t **insched)
 498 {
 499         ftdm_sched_t *sched = NULL;
 500         ftdm_timer_t *timer;
 501         ftdm_timer_t *deltimer;
 502         ftdm_assert_return(insched != NULL, FTDM_EINVAL, "sched is null!\n");
 503         ftdm_assert_return(*insched != NULL, FTDM_EINVAL, "sched is null!\n");
 504 
 505         sched = *insched;
 506 
 507         /* since destroying a sched may affect the global list, we gotta check */       
 508         ftdm_mutex_lock(sched_globals.mutex);
 509 
 510         /* if we're head, replace head with our next (whatever our next is, even null will do) */
 511         if (sched == sched_globals.freeruns) {
 512                 sched_globals.freeruns = sched->next;
 513         }
 514         /* if we have a previous member (then we were not head) set our previous next to our next */
 515         if (sched->prev) {
 516                 sched->prev->next = sched->next;
 517         }
 518         /* 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) */
 519         if (sched->next) {
 520                 sched->next->prev = sched->prev;
 521         }
 522 
 523         ftdm_mutex_unlock(sched_globals.mutex);
 524 
 525         /* now grab the sched mutex */
 526         ftdm_mutex_lock(sched->mutex);
 527 
 528         timer = sched->timers;
 529         while (timer) {
 530                 deltimer = timer;
 531                 timer = timer->next;
 532                 ftdm_safe_free(deltimer);
 533         }
 534 
 535         ftdm_log(FTDM_LOG_DEBUG, "Destroying schedule %s\n", sched->name);
 536 
 537         ftdm_mutex_unlock(sched->mutex);
 538 
 539         ftdm_mutex_destroy(&sched->mutex);
 540 
 541         ftdm_safe_free(sched);
 542 
 543         *insched = NULL;
 544         return FTDM_SUCCESS;
 545 }
 546 
 547 /* For Emacs:
 548  * Local Variables:
 549  * mode:c
 550  * indent-tabs-mode:t
 551  * tab-width:4
 552  * c-basic-offset:4
 553  * End:
 554  * For VIM:
 555  * vim:set softtabstop=4 shiftwidth=4 tabstop=4:
 556  */

/* [<][>][^][v][top][bottom][index][help] */