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);
242 /* Delete from fd queue */
243 ftask = (SilcTaskFd)task;
244 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd));
247 /* Timeout freelist garbage collection */
249 SILC_TASK_CALLBACK(silc_schedule_timeout_gc)
254 if (!schedule->valid)
257 SILC_LOG_DEBUG(("Timeout freelist garbage collection"));
259 SILC_SCHEDULE_LOCK(schedule);
261 if (silc_list_count(schedule->free_tasks) <= 10) {
262 SILC_SCHEDULE_UNLOCK(schedule);
263 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
267 if (silc_list_count(schedule->timeout_queue) >
268 silc_list_count(schedule->free_tasks)) {
269 SILC_SCHEDULE_UNLOCK(schedule);
270 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
275 c = silc_list_count(schedule->free_tasks) / 2;
276 if (c > silc_list_count(schedule->timeout_queue))
277 c = (silc_list_count(schedule->free_tasks) -
278 silc_list_count(schedule->timeout_queue));
279 if (silc_list_count(schedule->free_tasks) - c < 10)
280 c -= (10 - (silc_list_count(schedule->free_tasks) - c));
282 SILC_LOG_DEBUG(("Freeing %d unused tasks, leaving %d", c,
283 silc_list_count(schedule->free_tasks) - c));
285 silc_list_start(schedule->free_tasks);
286 while ((t = silc_list_get(schedule->free_tasks)) && c-- > 0) {
287 silc_list_del(schedule->free_tasks, t);
290 silc_list_start(schedule->free_tasks);
292 SILC_SCHEDULE_UNLOCK(schedule);
294 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
298 #ifdef SILC_DIST_INPLACE
299 /* Print schedule statistics to stdout */
301 void silc_schedule_stats(SilcSchedule schedule)
304 fprintf(stdout, "Schedule %p statistics:\n\n", schedule);
305 fprintf(stdout, "Num FD tasks : %lu (%lu bytes allocated)\n",
306 silc_hash_table_count(schedule->fd_queue),
307 sizeof(*ftask) * silc_hash_table_count(schedule->fd_queue));
308 fprintf(stdout, "Num Timeout tasks : %d (%d bytes allocated)\n",
309 silc_list_count(schedule->timeout_queue),
310 sizeof(struct SilcTaskTimeoutStruct) *
311 silc_list_count(schedule->timeout_queue));
312 fprintf(stdout, "Num Timeout freelist : %d (%d bytes allocated)\n",
313 silc_list_count(schedule->free_tasks),
314 sizeof(struct SilcTaskTimeoutStruct) *
315 silc_list_count(schedule->free_tasks));
317 #endif /* SILC_DIST_INPLACE */
319 /****************************** Public API **********************************/
321 /* Initializes the scheduler. This returns the scheduler context that
322 is given as arugment usually to all silc_schedule_* functions.
323 The `max_tasks' indicates the number of maximum tasks that the
324 scheduler can handle. The `app_context' is application specific
325 context that is delivered to task callbacks. */
327 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
329 SilcSchedule schedule;
331 SILC_LOG_DEBUG(("Initializing scheduler"));
333 schedule = silc_calloc(1, sizeof(*schedule));
338 silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
339 silc_schedule_fd_destructor, NULL, TRUE);
340 if (!schedule->fd_queue)
343 silc_list_init(schedule->timeout_queue, struct SilcTaskTimeoutStruct, next);
344 silc_list_init(schedule->free_tasks, struct SilcTaskTimeoutStruct, next);
346 schedule->app_context = app_context;
347 schedule->valid = TRUE;
348 schedule->max_tasks = max_tasks;
350 /* Allocate scheduler lock */
351 silc_mutex_alloc(&schedule->lock);
353 /* Initialize the platform specific scheduler. */
354 schedule->internal = schedule_ops.init(schedule, app_context);
356 /* Timeout freelist garbage collection */
357 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
363 /* Uninitializes the schedule. This is called when the program is ready
364 to end. This removes all tasks and task queues. Returns FALSE if the
365 scheduler could not be uninitialized. This happens when the scheduler
366 is still valid and silc_schedule_stop has not been called. */
368 SilcBool silc_schedule_uninit(SilcSchedule schedule)
372 SILC_LOG_DEBUG(("Uninitializing scheduler"));
374 if (schedule->valid == TRUE)
377 /* Dispatch all timeouts before going away */
378 SILC_SCHEDULE_LOCK(schedule);
379 silc_schedule_dispatch_timeout(schedule, TRUE);
380 SILC_SCHEDULE_UNLOCK(schedule);
382 /* Deliver signals before going away */
383 if (schedule->signal_tasks) {
384 schedule_ops.signals_call(schedule, schedule->internal);
385 schedule->signal_tasks = FALSE;
388 /* Unregister all tasks */
389 silc_schedule_task_del(schedule, SILC_ALL_TASKS);
390 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
392 /* Delete timeout task freelist */
393 silc_list_start(schedule->free_tasks);
394 while ((task = silc_list_get(schedule->free_tasks)))
397 /* Unregister all task queues */
398 silc_hash_table_free(schedule->fd_queue);
400 /* Uninit the platform specific scheduler. */
401 schedule_ops.uninit(schedule, schedule->internal);
403 silc_mutex_free(schedule->lock);
409 /* Stops the schedule even if it is not supposed to be stopped yet.
410 After calling this, one should call silc_schedule_uninit (after the
411 silc_schedule has returned). */
413 void silc_schedule_stop(SilcSchedule schedule)
415 SILC_LOG_DEBUG(("Stopping scheduler"));
416 SILC_SCHEDULE_LOCK(schedule);
417 schedule->valid = FALSE;
418 SILC_SCHEDULE_UNLOCK(schedule);
421 /* Runs the scheduler once and then returns. */
423 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
425 struct timeval timeout;
428 if (!schedule->is_locked)
429 SILC_SCHEDULE_LOCK(schedule);
432 SILC_LOG_DEBUG(("In scheduler loop"));
434 /* Deliver signals if any has been set to be called */
435 if (schedule->signal_tasks) {
436 SILC_SCHEDULE_UNLOCK(schedule);
437 schedule_ops.signals_call(schedule, schedule->internal);
438 schedule->signal_tasks = FALSE;
439 SILC_SCHEDULE_LOCK(schedule);
442 /* Check if scheduler is valid */
443 if (schedule->valid == FALSE) {
444 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
445 if (!schedule->is_locked)
446 SILC_SCHEDULE_UNLOCK(schedule);
450 /* Calculate next timeout for silc_select(). This is the timeout value
451 when at earliest some of the timeout tasks expire. This may dispatch
452 already expired timeouts. */
453 silc_schedule_select_timeout(schedule);
455 /* Check if scheduler is valid */
456 if (schedule->valid == FALSE) {
457 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
458 if (!schedule->is_locked)
459 SILC_SCHEDULE_UNLOCK(schedule);
463 if (timeout_usecs >= 0) {
465 timeout.tv_usec = timeout_usecs;
466 schedule->timeout = timeout;
467 schedule->has_timeout = TRUE;
470 /* This is the main silc_select(). The program blocks here until some
471 of the selected file descriptors change status or the selected
473 SILC_LOG_DEBUG(("Select"));
474 ret = schedule_ops.select(schedule, schedule->internal);
479 SILC_LOG_DEBUG(("Running timeout tasks"));
480 if (silc_list_count(schedule->timeout_queue))
481 silc_schedule_dispatch_timeout(schedule, FALSE);
487 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
490 /* There is some data available now */
491 SILC_LOG_DEBUG(("Running fd tasks"));
492 silc_schedule_dispatch_fd(schedule);
495 } while (timeout_usecs == -1);
497 if (!schedule->is_locked)
498 SILC_SCHEDULE_UNLOCK(schedule);
503 /* The SILC scheduler. This is actually the main routine in SILC programs.
504 When this returns the program is to be ended. Before this function can
505 be called, one must call silc_schedule_init function. */
507 void silc_schedule(SilcSchedule schedule)
509 SILC_LOG_DEBUG(("Running scheduler"));
511 if (schedule->valid == FALSE) {
512 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
516 /* Start the scheduler loop */
517 SILC_SCHEDULE_LOCK(schedule);
518 schedule->is_locked = TRUE;
519 silc_schedule_one(schedule, -1);
520 SILC_SCHEDULE_UNLOCK(schedule);
523 /* Wakes up the scheduler. This is used only in multi-threaded
524 environments where threads may add new tasks or remove old tasks
525 from task queues. This is called to wake up the scheduler in the
526 main thread so that it detects the changes in the task queues.
527 If threads support is not compiled in this function has no effect.
528 Implementation of this function is platform specific. */
530 void silc_schedule_wakeup(SilcSchedule schedule)
533 SILC_LOG_DEBUG(("Wakeup scheduler"));
534 SILC_SCHEDULE_LOCK(schedule);
535 schedule_ops.wakeup(schedule, schedule->internal);
536 SILC_SCHEDULE_UNLOCK(schedule);
540 /* Returns the application specific context that was saved into the
541 scheduler in silc_schedule_init function. The context is also
542 returned to application in task callback functions, but this function
543 may be used to get it as well if needed. */
545 void *silc_schedule_get_context(SilcSchedule schedule)
547 return schedule->app_context;
550 /* Add new task to the scheduler */
552 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
553 SilcTaskCallback callback, void *context,
554 long seconds, long useconds,
557 SilcTask task = NULL;
559 if (!schedule->valid)
562 SILC_SCHEDULE_LOCK(schedule);
564 if (type == SILC_TASK_TIMEOUT) {
565 SilcTaskTimeout tmp, prev, ttask;
567 silc_list_start(schedule->free_tasks);
568 ttask = silc_list_get(schedule->free_tasks);
570 ttask = silc_calloc(1, sizeof(*ttask));
574 silc_list_del(schedule->free_tasks, ttask);
576 ttask->header.type = 1;
577 ttask->header.callback = callback;
578 ttask->header.context = context;
579 ttask->header.valid = TRUE;
582 silc_gettimeofday(&ttask->timeout);
583 if ((seconds + useconds) > 0) {
584 ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
585 ttask->timeout.tv_usec += (useconds % 1000000L);
586 if (ttask->timeout.tv_usec >= 1000000L) {
587 ttask->timeout.tv_sec += 1;
588 ttask->timeout.tv_usec -= 1000000L;
592 SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
595 /* Add task to correct spot so that the first task in the list has
596 the earliest timeout. */
597 silc_list_start(schedule->timeout_queue);
599 while ((tmp = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
600 /* If we have shorter timeout, we have found our spot */
601 if (silc_compare_timeval(&ttask->timeout, &tmp->timeout)) {
602 silc_list_insert(schedule->timeout_queue, prev, ttask);
608 silc_list_add(schedule->timeout_queue, ttask);
610 task = (SilcTask)ttask;
612 } else if (type == SILC_TASK_FD) {
613 /* Check if fd is already added */
614 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
615 NULL, (void **)&task))
618 /* Check max tasks */
619 if (schedule->max_tasks > 0 &&
620 silc_hash_table_count(schedule->fd_queue) >= schedule->max_tasks) {
621 SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
625 SilcTaskFd ftask = silc_calloc(1, sizeof(*ftask));
629 SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
631 ftask->header.type = 0;
632 ftask->header.callback = callback;
633 ftask->header.context = context;
634 ftask->header.valid = TRUE;
635 ftask->events = SILC_TASK_READ;
639 silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask);
641 task = (SilcTask)ftask;
643 } else if (type == SILC_TASK_SIGNAL) {
644 SILC_SCHEDULE_UNLOCK(schedule);
645 schedule_ops.signal_register(schedule, schedule->internal, (int)fd,
652 SILC_SCHEDULE_UNLOCK(schedule);
656 /* Invalidates task */
658 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
660 if (task == SILC_ALL_TASKS) {
661 SilcHashTableList htl;
663 SILC_LOG_DEBUG(("Unregister all tasks"));
665 SILC_SCHEDULE_LOCK(schedule);
667 /* Delete from fd queue */
668 silc_hash_table_list(schedule->fd_queue, &htl);
669 while (silc_hash_table_get(&htl, NULL, (void **)&task))
671 silc_hash_table_list_reset(&htl);
673 /* Delete from timeout queue */
674 silc_list_start(schedule->timeout_queue);
675 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
679 SILC_SCHEDULE_UNLOCK(schedule);
683 SILC_LOG_DEBUG(("Unregistering task %p", task));
684 SILC_SCHEDULE_LOCK(schedule);
686 SILC_SCHEDULE_UNLOCK(schedule);
689 /* Invalidate task by fd */
691 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
693 SilcTask task = NULL;
695 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
697 SILC_SCHEDULE_LOCK(schedule);
699 /* fd is unique, so there is only one task with this fd in the table */
700 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd), NULL,
704 SILC_SCHEDULE_UNLOCK(schedule);
706 /* If it is signal, remove it */
708 schedule_ops.signal_unregister(schedule, schedule->internal, fd);
711 /* Invalidate task by task callback. */
713 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
714 SilcTaskCallback callback)
717 SilcHashTableList htl;
719 SILC_LOG_DEBUG(("Unregister task by callback"));
721 SILC_SCHEDULE_LOCK(schedule);
723 /* Delete from fd queue */
724 silc_hash_table_list(schedule->fd_queue, &htl);
725 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
726 if (task->callback == callback)
729 silc_hash_table_list_reset(&htl);
731 /* Delete from timeout queue */
732 silc_list_start(schedule->timeout_queue);
733 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
735 if (task->callback == callback)
739 SILC_SCHEDULE_UNLOCK(schedule);
742 /* Invalidate task by context. */
744 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
747 SilcHashTableList htl;
749 SILC_LOG_DEBUG(("Unregister task by context"));
751 SILC_SCHEDULE_LOCK(schedule);
753 /* Delete from fd queue */
754 silc_hash_table_list(schedule->fd_queue, &htl);
755 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
756 if (task->context == context)
759 silc_hash_table_list_reset(&htl);
761 /* Delete from timeout queue */
762 silc_list_start(schedule->timeout_queue);
763 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
765 if (task->context == context)
769 SILC_SCHEDULE_UNLOCK(schedule);
772 /* Invalidate task by all */
774 void silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
775 SilcTaskCallback callback, void *context)
779 SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
781 /* For fd task, callback and context is irrelevant as fd is unique */
783 silc_schedule_task_del_by_fd(schedule, fd);
785 SILC_SCHEDULE_LOCK(schedule);
787 /* Delete from timeout queue */
788 silc_list_start(schedule->timeout_queue);
789 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
791 if (task->callback == callback && task->context == context)
795 SILC_SCHEDULE_UNLOCK(schedule);
798 /* Sets a file descriptor to be listened by scheduler. One can call this
799 directly if wanted. This can be called multiple times for one file
800 descriptor to set different iomasks. */
802 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
803 SilcTaskEvent mask, SilcBool send_events)
807 if (!schedule->valid)
810 SILC_SCHEDULE_LOCK(schedule);
812 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
813 NULL, (void **)&task)) {
816 task->revents = mask;
817 silc_schedule_dispatch_fd(schedule);
821 SILC_SCHEDULE_UNLOCK(schedule);
824 /* Removes a file descriptor from listen list. */
826 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
828 silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);