5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 1998 - 2006 Pekka Riikonen
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; version 2 of the License.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
23 /************************** Types and definitions ***************************/
25 /* Platform specific implementation */
26 extern const SilcScheduleOps schedule_ops;
28 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task);
29 static void silc_schedule_dispatch_fd(SilcSchedule schedule);
30 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
31 SilcBool dispatch_all);
34 /************************ Static utility functions **************************/
36 /* Fd task hash table destructor */
38 static void silc_schedule_fd_destructor(void *key, void *context,
44 /* Executes file descriptor tasks. Invalid tasks are removed here. */
46 static void silc_schedule_dispatch_fd(SilcSchedule schedule)
48 SilcHashTableList htl;
53 silc_hash_table_list(schedule->fd_queue, &htl);
54 while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
58 silc_schedule_task_remove(schedule, t);
61 if (!task->events || !task->revents)
64 /* Is the task ready for reading */
65 if (task->revents & SILC_TASK_READ) {
66 SILC_SCHEDULE_UNLOCK(schedule);
67 t->callback(schedule, schedule->app_context, SILC_TASK_READ,
68 task->fd, t->context);
69 SILC_SCHEDULE_LOCK(schedule);
72 /* Is the task ready for writing */
73 if (t->valid && task->revents & SILC_TASK_WRITE) {
74 SILC_SCHEDULE_UNLOCK(schedule);
75 t->callback(schedule, schedule->app_context, SILC_TASK_WRITE,
76 task->fd, t->context);
77 SILC_SCHEDULE_LOCK(schedule);
80 /* Remove if task was invalidated in the task callback */
82 silc_schedule_task_remove(schedule, t);
84 silc_hash_table_list_reset(&htl);
87 /* Executes all tasks whose timeout has expired. The task is removed from
88 the task queue after the callback function has returned. Also, invalid
89 tasks are removed here. */
91 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
92 SilcBool dispatch_all)
96 struct timeval curtime;
99 SILC_LOG_DEBUG(("Running timeout tasks"));
101 silc_gettimeofday(&curtime);
103 /* First task in the task queue has always the earliest timeout. */
104 silc_list_start(schedule->timeout_queue);
105 while ((task = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
108 /* Remove invalid task */
110 silc_schedule_task_remove(schedule, t);
114 /* Execute the task if the timeout has expired */
115 if (dispatch_all || silc_compare_timeval(&task->timeout, &curtime)) {
117 SILC_SCHEDULE_UNLOCK(schedule);
118 t->callback(schedule, schedule->app_context, SILC_TASK_EXPIRE, 0,
120 SILC_SCHEDULE_LOCK(schedule);
122 /* Remove the expired task */
123 silc_schedule_task_remove(schedule, t);
125 /* Balance when we have lots of small timeouts */
132 /* Calculates next timeout. This is the timeout value when at earliest some
133 of the timeout tasks expire. If this is in the past, they will be
136 static void silc_schedule_select_timeout(SilcSchedule schedule)
139 SilcTaskTimeout task;
140 struct timeval curtime;
141 SilcBool dispatch = TRUE;
143 /* Get the current time */
144 silc_gettimeofday(&curtime);
145 schedule->has_timeout = FALSE;
147 /* First task in the task queue has always the earliest timeout. */
148 silc_list_start(schedule->timeout_queue);
149 while ((task = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
152 /* Remove invalid task */
154 silc_schedule_task_remove(schedule, t);
158 /* If the timeout is in past, we will run the task and all other
159 timeout tasks from the past. */
160 if (silc_compare_timeval(&task->timeout, &curtime) && dispatch) {
161 silc_schedule_dispatch_timeout(schedule, FALSE);
162 if (!schedule->valid)
165 /* Start selecting new timeout again after dispatch */
166 silc_list_start(schedule->timeout_queue);
171 /* Calculate the next timeout */
172 curtime.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
173 curtime.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
174 if (curtime.tv_sec < 0)
177 /* We wouldn't want to go under zero, check for it. */
178 if (curtime.tv_usec < 0) {
180 if (curtime.tv_sec < 0)
182 curtime.tv_usec += 1000000L;
188 /* Save the timeout */
190 schedule->timeout = curtime;
191 schedule->has_timeout = TRUE;
192 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout.tv_sec,
193 schedule->timeout.tv_usec));
197 /* Removes task from the scheduler. This must be called with scheduler
200 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task)
203 SilcTaskTimeout ttask;
205 if (task == SILC_ALL_TASKS) {
207 SilcHashTableList htl;
210 /* Delete from fd queue */
211 silc_hash_table_list(schedule->fd_queue, &htl);
212 while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task))
213 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd));
214 silc_hash_table_list_reset(&htl);
216 /* Delete from timeout queue */
217 silc_list_start(schedule->timeout_queue);
218 while ((task = silc_list_get(schedule->timeout_queue))) {
219 silc_list_del(schedule->timeout_queue, task);
226 /* Delete from timeout queue */
227 if (task->type == 1) {
228 silc_list_start(schedule->timeout_queue);
229 while ((ttask = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
230 if (ttask == (SilcTaskTimeout)task) {
231 silc_list_del(schedule->timeout_queue, ttask);
233 /* Put to free list */
234 silc_list_add(schedule->free_tasks, ttask);
235 if (silc_list_count(schedule->free_tasks) == 1)
236 silc_list_start(schedule->free_tasks);
244 /* Delete from fd queue */
245 ftask = (SilcTaskFd)task;
246 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd));
249 /* Timeout freelist garbage collection */
251 SILC_TASK_CALLBACK(silc_schedule_timeout_gc)
256 if (!schedule->valid)
259 SILC_LOG_DEBUG(("Timeout freelist garbage collection"));
261 SILC_SCHEDULE_LOCK(schedule);
263 if (silc_list_count(schedule->free_tasks) <= 10) {
264 SILC_SCHEDULE_UNLOCK(schedule);
265 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
269 if (silc_list_count(schedule->timeout_queue) >
270 silc_list_count(schedule->free_tasks)) {
271 SILC_SCHEDULE_UNLOCK(schedule);
272 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
277 c = silc_list_count(schedule->free_tasks) / 2;
278 if (c > silc_list_count(schedule->timeout_queue))
279 c = (silc_list_count(schedule->free_tasks) -
280 silc_list_count(schedule->timeout_queue));
281 if (silc_list_count(schedule->free_tasks) - c < 10)
282 c -= (10 - (silc_list_count(schedule->free_tasks) - c));
284 SILC_LOG_DEBUG(("Freeing %d unused tasks, leaving %d", c,
285 silc_list_count(schedule->free_tasks) - c));
287 silc_list_start(schedule->free_tasks);
288 while ((t = silc_list_get(schedule->free_tasks)) && c-- > 0) {
289 silc_list_del(schedule->free_tasks, t);
292 silc_list_start(schedule->free_tasks);
294 SILC_SCHEDULE_UNLOCK(schedule);
296 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
300 #ifdef SILC_DIST_INPLACE
301 /* Print schedule statistics to stdout */
303 void silc_schedule_stats(SilcSchedule schedule)
306 fprintf(stdout, "Schedule %p statistics:\n\n", schedule);
307 fprintf(stdout, "Num FD tasks : %lu (%lu bytes allocated)\n",
308 silc_hash_table_count(schedule->fd_queue),
309 sizeof(*ftask) * silc_hash_table_count(schedule->fd_queue));
310 fprintf(stdout, "Num Timeout tasks : %d (%d bytes allocated)\n",
311 silc_list_count(schedule->timeout_queue),
312 sizeof(struct SilcTaskTimeoutStruct) *
313 silc_list_count(schedule->timeout_queue));
314 fprintf(stdout, "Num Timeout freelist : %d (%d bytes allocated)\n",
315 silc_list_count(schedule->free_tasks),
316 sizeof(struct SilcTaskTimeoutStruct) *
317 silc_list_count(schedule->free_tasks));
319 #endif /* SILC_DIST_INPLACE */
321 /****************************** Public API **********************************/
323 /* Initializes the scheduler. This returns the scheduler context that
324 is given as arugment usually to all silc_schedule_* functions.
325 The `max_tasks' indicates the number of maximum tasks that the
326 scheduler can handle. The `app_context' is application specific
327 context that is delivered to task callbacks. */
329 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
331 SilcSchedule schedule;
333 SILC_LOG_DEBUG(("Initializing scheduler"));
335 schedule = silc_calloc(1, sizeof(*schedule));
340 silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
341 silc_schedule_fd_destructor, NULL, TRUE);
342 if (!schedule->fd_queue)
345 silc_list_init(schedule->timeout_queue, struct SilcTaskTimeoutStruct, next);
346 silc_list_init(schedule->free_tasks, struct SilcTaskTimeoutStruct, next);
348 schedule->app_context = app_context;
349 schedule->valid = TRUE;
350 schedule->max_tasks = max_tasks;
352 /* Allocate scheduler lock */
353 silc_mutex_alloc(&schedule->lock);
355 /* Initialize the platform specific scheduler. */
356 schedule->internal = schedule_ops.init(schedule, app_context);
358 /* Timeout freelist garbage collection */
359 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
365 /* Uninitializes the schedule. This is called when the program is ready
366 to end. This removes all tasks and task queues. Returns FALSE if the
367 scheduler could not be uninitialized. This happens when the scheduler
368 is still valid and silc_schedule_stop has not been called. */
370 SilcBool silc_schedule_uninit(SilcSchedule schedule)
374 SILC_LOG_DEBUG(("Uninitializing scheduler"));
376 if (schedule->valid == TRUE)
379 /* Dispatch all timeouts before going away */
380 SILC_SCHEDULE_LOCK(schedule);
381 silc_schedule_dispatch_timeout(schedule, TRUE);
382 SILC_SCHEDULE_UNLOCK(schedule);
384 /* Deliver signals before going away */
385 if (schedule->signal_tasks) {
386 schedule_ops.signals_call(schedule, schedule->internal);
387 schedule->signal_tasks = FALSE;
390 /* Unregister all tasks */
391 silc_schedule_task_del(schedule, SILC_ALL_TASKS);
392 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
394 /* Delete timeout task freelist */
395 silc_list_start(schedule->free_tasks);
396 while ((task = silc_list_get(schedule->free_tasks)))
399 /* Unregister all task queues */
400 silc_hash_table_free(schedule->fd_queue);
402 /* Uninit the platform specific scheduler. */
403 schedule_ops.uninit(schedule, schedule->internal);
405 silc_mutex_free(schedule->lock);
411 /* Stops the schedule even if it is not supposed to be stopped yet.
412 After calling this, one should call silc_schedule_uninit (after the
413 silc_schedule has returned). */
415 void silc_schedule_stop(SilcSchedule schedule)
417 SILC_LOG_DEBUG(("Stopping scheduler"));
418 SILC_SCHEDULE_LOCK(schedule);
419 schedule->valid = FALSE;
420 SILC_SCHEDULE_UNLOCK(schedule);
423 /* Runs the scheduler once and then returns. */
425 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
427 struct timeval timeout;
430 if (!schedule->is_locked)
431 SILC_SCHEDULE_LOCK(schedule);
434 SILC_LOG_DEBUG(("In scheduler loop"));
436 /* Deliver signals if any has been set to be called */
437 if (schedule->signal_tasks) {
438 SILC_SCHEDULE_UNLOCK(schedule);
439 schedule_ops.signals_call(schedule, schedule->internal);
440 schedule->signal_tasks = FALSE;
441 SILC_SCHEDULE_LOCK(schedule);
444 /* Check if scheduler is valid */
445 if (schedule->valid == FALSE) {
446 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
447 if (!schedule->is_locked)
448 SILC_SCHEDULE_UNLOCK(schedule);
452 /* Calculate next timeout for silc_select(). This is the timeout value
453 when at earliest some of the timeout tasks expire. This may dispatch
454 already expired timeouts. */
455 silc_schedule_select_timeout(schedule);
457 /* Check if scheduler is valid */
458 if (schedule->valid == FALSE) {
459 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
460 if (!schedule->is_locked)
461 SILC_SCHEDULE_UNLOCK(schedule);
465 if (timeout_usecs >= 0) {
467 timeout.tv_usec = timeout_usecs;
468 schedule->timeout = timeout;
469 schedule->has_timeout = TRUE;
472 /* This is the main silc_select(). The program blocks here until some
473 of the selected file descriptors change status or the selected
475 SILC_LOG_DEBUG(("Select"));
476 ret = schedule_ops.select(schedule, schedule->internal);
481 SILC_LOG_DEBUG(("Running timeout tasks"));
482 if (silc_list_count(schedule->timeout_queue))
483 silc_schedule_dispatch_timeout(schedule, FALSE);
489 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
492 /* There is some data available now */
493 SILC_LOG_DEBUG(("Running fd tasks"));
494 silc_schedule_dispatch_fd(schedule);
497 } while (timeout_usecs == -1);
499 if (!schedule->is_locked)
500 SILC_SCHEDULE_UNLOCK(schedule);
505 /* The SILC scheduler. This is actually the main routine in SILC programs.
506 When this returns the program is to be ended. Before this function can
507 be called, one must call silc_schedule_init function. */
509 void silc_schedule(SilcSchedule schedule)
511 SILC_LOG_DEBUG(("Running scheduler"));
513 if (schedule->valid == FALSE) {
514 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
518 /* Start the scheduler loop */
519 SILC_SCHEDULE_LOCK(schedule);
520 schedule->is_locked = TRUE;
521 silc_schedule_one(schedule, -1);
522 SILC_SCHEDULE_UNLOCK(schedule);
525 /* Wakes up the scheduler. This is used only in multi-threaded
526 environments where threads may add new tasks or remove old tasks
527 from task queues. This is called to wake up the scheduler in the
528 main thread so that it detects the changes in the task queues.
529 If threads support is not compiled in this function has no effect.
530 Implementation of this function is platform specific. */
532 void silc_schedule_wakeup(SilcSchedule schedule)
535 SILC_LOG_DEBUG(("Wakeup scheduler"));
536 SILC_SCHEDULE_LOCK(schedule);
537 schedule_ops.wakeup(schedule, schedule->internal);
538 SILC_SCHEDULE_UNLOCK(schedule);
542 /* Returns the application specific context that was saved into the
543 scheduler in silc_schedule_init function. The context is also
544 returned to application in task callback functions, but this function
545 may be used to get it as well if needed. */
547 void *silc_schedule_get_context(SilcSchedule schedule)
549 return schedule->app_context;
552 /* Add new task to the scheduler */
554 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
555 SilcTaskCallback callback, void *context,
556 long seconds, long useconds,
559 SilcTask task = NULL;
561 if (!schedule->valid)
564 SILC_SCHEDULE_LOCK(schedule);
566 if (type == SILC_TASK_TIMEOUT) {
567 SilcTaskTimeout tmp, prev, ttask;
569 ttask = silc_list_get(schedule->free_tasks);
571 ttask = silc_calloc(1, sizeof(*ttask));
575 silc_list_del(schedule->free_tasks, ttask);
577 ttask->header.type = 1;
578 ttask->header.callback = callback;
579 ttask->header.context = context;
580 ttask->header.valid = TRUE;
583 silc_gettimeofday(&ttask->timeout);
584 if ((seconds + useconds) > 0) {
585 ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
586 ttask->timeout.tv_usec += (useconds % 1000000L);
587 if (ttask->timeout.tv_usec >= 1000000L) {
588 ttask->timeout.tv_sec += 1;
589 ttask->timeout.tv_usec -= 1000000L;
593 SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
596 /* Add task to correct spot so that the first task in the list has
597 the earliest timeout. */
598 silc_list_start(schedule->timeout_queue);
600 while ((tmp = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
601 /* If we have shorter timeout, we have found our spot */
602 if (silc_compare_timeval(&ttask->timeout, &tmp->timeout)) {
603 silc_list_insert(schedule->timeout_queue, prev, ttask);
609 silc_list_add(schedule->timeout_queue, ttask);
611 task = (SilcTask)ttask;
613 } else if (type == SILC_TASK_FD) {
614 /* Check if fd is already added */
615 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
616 NULL, (void **)&task))
619 /* Check max tasks */
620 if (schedule->max_tasks > 0 &&
621 silc_hash_table_count(schedule->fd_queue) >= schedule->max_tasks) {
622 SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
626 SilcTaskFd ftask = silc_calloc(1, sizeof(*ftask));
630 SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
632 ftask->header.type = 0;
633 ftask->header.callback = callback;
634 ftask->header.context = context;
635 ftask->header.valid = TRUE;
636 ftask->events = SILC_TASK_READ;
640 silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask);
642 task = (SilcTask)ftask;
644 } else if (type == SILC_TASK_SIGNAL) {
645 SILC_SCHEDULE_UNLOCK(schedule);
646 schedule_ops.signal_register(schedule, schedule->internal, (int)fd,
653 SILC_SCHEDULE_UNLOCK(schedule);
657 /* Invalidates task */
659 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
661 if (task == SILC_ALL_TASKS) {
662 SilcHashTableList htl;
664 SILC_LOG_DEBUG(("Unregister all tasks"));
666 SILC_SCHEDULE_LOCK(schedule);
668 /* Delete from fd queue */
669 silc_hash_table_list(schedule->fd_queue, &htl);
670 while (silc_hash_table_get(&htl, NULL, (void **)&task))
672 silc_hash_table_list_reset(&htl);
674 /* Delete from timeout queue */
675 silc_list_start(schedule->timeout_queue);
676 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
680 SILC_SCHEDULE_UNLOCK(schedule);
684 SILC_LOG_DEBUG(("Unregistering task %p", task));
685 SILC_SCHEDULE_LOCK(schedule);
687 SILC_SCHEDULE_UNLOCK(schedule);
690 /* Invalidate task by fd */
692 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
694 SilcTask task = NULL;
696 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
698 SILC_SCHEDULE_LOCK(schedule);
700 /* fd is unique, so there is only one task with this fd in the table */
701 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd), NULL,
705 SILC_SCHEDULE_UNLOCK(schedule);
707 /* If it is signal, remove it */
709 schedule_ops.signal_unregister(schedule, schedule->internal, fd);
712 /* Invalidate task by task callback. */
714 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
715 SilcTaskCallback callback)
718 SilcHashTableList htl;
720 SILC_LOG_DEBUG(("Unregister task by callback"));
722 SILC_SCHEDULE_LOCK(schedule);
724 /* Delete from fd queue */
725 silc_hash_table_list(schedule->fd_queue, &htl);
726 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
727 if (task->callback == callback)
730 silc_hash_table_list_reset(&htl);
732 /* Delete from timeout queue */
733 silc_list_start(schedule->timeout_queue);
734 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
736 if (task->callback == callback)
740 SILC_SCHEDULE_UNLOCK(schedule);
743 /* Invalidate task by context. */
745 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
748 SilcHashTableList htl;
750 SILC_LOG_DEBUG(("Unregister task by context"));
752 SILC_SCHEDULE_LOCK(schedule);
754 /* Delete from fd queue */
755 silc_hash_table_list(schedule->fd_queue, &htl);
756 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
757 if (task->context == context)
760 silc_hash_table_list_reset(&htl);
762 /* Delete from timeout queue */
763 silc_list_start(schedule->timeout_queue);
764 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
766 if (task->context == context)
770 SILC_SCHEDULE_UNLOCK(schedule);
773 /* Invalidate task by all */
775 void silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
776 SilcTaskCallback callback, void *context)
780 SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
782 /* For fd task, callback and context is irrelevant as fd is unique */
784 silc_schedule_task_del_by_fd(schedule, fd);
786 SILC_SCHEDULE_LOCK(schedule);
788 /* Delete from timeout queue */
789 silc_list_start(schedule->timeout_queue);
790 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
792 if (task->callback == callback && task->context == context)
796 SILC_SCHEDULE_UNLOCK(schedule);
799 /* Sets a file descriptor to be listened by scheduler. One can call this
800 directly if wanted. This can be called multiple times for one file
801 descriptor to set different iomasks. */
803 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
804 SilcTaskEvent mask, SilcBool send_events)
808 if (!schedule->valid)
811 SILC_SCHEDULE_LOCK(schedule);
813 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
814 NULL, (void **)&task)) {
817 task->revents = mask;
818 silc_schedule_dispatch_fd(schedule);
822 SILC_SCHEDULE_UNLOCK(schedule);
825 /* Removes a file descriptor from listen list. */
827 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
829 silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);