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 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout.tv_sec,
192 schedule->timeout.tv_usec));
196 /* Removes task from the scheduler. This must be called with scheduler
199 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task)
202 SilcTaskTimeout ttask;
204 if (task == SILC_ALL_TASKS) {
206 SilcHashTableList htl;
209 /* Delete from fd queue */
210 silc_hash_table_list(schedule->fd_queue, &htl);
211 while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task))
212 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd));
213 silc_hash_table_list_reset(&htl);
215 /* Delete from timeout queue */
216 silc_list_start(schedule->timeout_queue);
217 while ((task = silc_list_get(schedule->timeout_queue))) {
218 silc_list_del(schedule->timeout_queue, task);
225 /* Delete from timeout queue */
226 if (task->type == 1) {
227 silc_list_start(schedule->timeout_queue);
228 while ((ttask = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
229 if (ttask == (SilcTaskTimeout)task) {
230 silc_list_del(schedule->timeout_queue, ttask);
232 /* Put to free list */
233 silc_list_add(schedule->free_tasks, ttask);
234 if (silc_list_count(schedule->free_tasks) == 1)
235 silc_list_start(schedule->free_tasks);
243 /* Delete from fd queue */
244 ftask = (SilcTaskFd)task;
245 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd));
248 /* Timeout freelist garbage collection */
250 SILC_TASK_CALLBACK(silc_schedule_timeout_gc)
255 if (!schedule->valid)
258 SILC_LOG_DEBUG(("Timeout freelist garbage collection"));
260 SILC_SCHEDULE_LOCK(schedule);
262 if (silc_list_count(schedule->free_tasks) <= 10) {
263 SILC_SCHEDULE_UNLOCK(schedule);
264 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
268 if (silc_list_count(schedule->timeout_queue) >
269 silc_list_count(schedule->free_tasks)) {
270 SILC_SCHEDULE_UNLOCK(schedule);
271 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
276 c = silc_list_count(schedule->free_tasks) / 2;
277 if (c > silc_list_count(schedule->timeout_queue))
278 c = (silc_list_count(schedule->free_tasks) -
279 silc_list_count(schedule->timeout_queue));
280 if (silc_list_count(schedule->free_tasks) - c < 10)
281 c -= (10 - (silc_list_count(schedule->free_tasks) - c));
283 SILC_LOG_DEBUG(("Freeing %d unused tasks, leaving %d", c,
284 silc_list_count(schedule->free_tasks) - c));
286 silc_list_start(schedule->free_tasks);
287 while ((t = silc_list_get(schedule->free_tasks)) && c-- > 0) {
288 silc_list_del(schedule->free_tasks, t);
291 silc_list_start(schedule->free_tasks);
293 SILC_SCHEDULE_UNLOCK(schedule);
295 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
299 #ifdef SILC_DIST_INPLACE
300 /* Print schedule statistics to stdout */
302 void silc_schedule_stats(SilcSchedule schedule)
305 fprintf(stdout, "Schedule %p statistics:\n\n", schedule);
306 fprintf(stdout, "Num FD tasks : %lu (%lu bytes allocated)\n",
307 silc_hash_table_count(schedule->fd_queue),
308 sizeof(*ftask) * silc_hash_table_count(schedule->fd_queue));
309 fprintf(stdout, "Num Timeout tasks : %d (%d bytes allocated)\n",
310 silc_list_count(schedule->timeout_queue),
311 sizeof(struct SilcTaskTimeoutStruct) *
312 silc_list_count(schedule->timeout_queue));
313 fprintf(stdout, "Num Timeout freelist : %d (%d bytes allocated)\n",
314 silc_list_count(schedule->free_tasks),
315 sizeof(struct SilcTaskTimeoutStruct) *
316 silc_list_count(schedule->free_tasks));
318 #endif /* SILC_DIST_INPLACE */
320 /****************************** Public API **********************************/
322 /* Initializes the scheduler. This returns the scheduler context that
323 is given as arugment usually to all silc_schedule_* functions.
324 The `max_tasks' indicates the number of maximum tasks that the
325 scheduler can handle. The `app_context' is application specific
326 context that is delivered to task callbacks. */
328 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
330 SilcSchedule schedule;
332 SILC_LOG_DEBUG(("Initializing scheduler"));
334 schedule = silc_calloc(1, sizeof(*schedule));
339 silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
340 silc_schedule_fd_destructor, NULL, TRUE);
341 if (!schedule->fd_queue)
344 silc_list_init(schedule->timeout_queue, struct SilcTaskTimeoutStruct, next);
345 silc_list_init(schedule->free_tasks, struct SilcTaskTimeoutStruct, next);
347 schedule->app_context = app_context;
348 schedule->valid = TRUE;
349 schedule->max_tasks = max_tasks;
351 /* Allocate scheduler lock */
352 silc_mutex_alloc(&schedule->lock);
354 /* Initialize the platform specific scheduler. */
355 schedule->internal = schedule_ops.init(schedule, app_context);
357 /* Timeout freelist garbage collection */
358 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
364 /* Uninitializes the schedule. This is called when the program is ready
365 to end. This removes all tasks and task queues. Returns FALSE if the
366 scheduler could not be uninitialized. This happens when the scheduler
367 is still valid and silc_schedule_stop has not been called. */
369 SilcBool silc_schedule_uninit(SilcSchedule schedule)
373 SILC_LOG_DEBUG(("Uninitializing scheduler"));
375 if (schedule->valid == TRUE)
378 /* Dispatch all timeouts before going away */
379 SILC_SCHEDULE_LOCK(schedule);
380 silc_schedule_dispatch_timeout(schedule, TRUE);
381 SILC_SCHEDULE_UNLOCK(schedule);
383 /* Deliver signals before going away */
384 if (schedule->signal_tasks) {
385 schedule_ops.signals_call(schedule, schedule->internal);
386 schedule->signal_tasks = FALSE;
389 /* Unregister all tasks */
390 silc_schedule_task_del(schedule, SILC_ALL_TASKS);
391 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
393 /* Delete timeout task freelist */
394 silc_list_start(schedule->free_tasks);
395 while ((task = silc_list_get(schedule->free_tasks)))
398 /* Unregister all task queues */
399 silc_hash_table_free(schedule->fd_queue);
401 /* Uninit the platform specific scheduler. */
402 schedule_ops.uninit(schedule, schedule->internal);
404 silc_mutex_free(schedule->lock);
410 /* Stops the schedule even if it is not supposed to be stopped yet.
411 After calling this, one should call silc_schedule_uninit (after the
412 silc_schedule has returned). */
414 void silc_schedule_stop(SilcSchedule schedule)
416 SILC_LOG_DEBUG(("Stopping scheduler"));
417 SILC_SCHEDULE_LOCK(schedule);
418 schedule->valid = FALSE;
419 SILC_SCHEDULE_UNLOCK(schedule);
422 /* Runs the scheduler once and then returns. */
424 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
426 struct timeval timeout;
429 SILC_LOG_DEBUG(("In scheduler loop"));
431 if (!schedule->is_locked)
432 SILC_SCHEDULE_LOCK(schedule);
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 silc_schedule_dispatch_timeout(schedule, FALSE);
486 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
489 /* There is some data available now */
490 SILC_LOG_DEBUG(("Running fd tasks"));
491 silc_schedule_dispatch_fd(schedule);
495 if (!schedule->is_locked)
496 SILC_SCHEDULE_UNLOCK(schedule);
501 /* The SILC scheduler. This is actually the main routine in SILC programs.
502 When this returns the program is to be ended. Before this function can
503 be called, one must call silc_schedule_init function. */
505 void silc_schedule(SilcSchedule schedule)
507 SILC_LOG_DEBUG(("Running scheduler"));
509 if (schedule->valid == FALSE) {
510 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
514 SILC_SCHEDULE_LOCK(schedule);
515 schedule->is_locked = TRUE;
517 /* Start the scheduler loop */
518 while (silc_schedule_one(schedule, -1))
521 SILC_SCHEDULE_UNLOCK(schedule);
524 /* Wakes up the scheduler. This is used only in multi-threaded
525 environments where threads may add new tasks or remove old tasks
526 from task queues. This is called to wake up the scheduler in the
527 main thread so that it detects the changes in the task queues.
528 If threads support is not compiled in this function has no effect.
529 Implementation of this function is platform specific. */
531 void silc_schedule_wakeup(SilcSchedule schedule)
534 SILC_LOG_DEBUG(("Wakeup scheduler"));
535 SILC_SCHEDULE_LOCK(schedule);
536 schedule_ops.wakeup(schedule, schedule->internal);
537 SILC_SCHEDULE_UNLOCK(schedule);
541 /* Returns the application specific context that was saved into the
542 scheduler in silc_schedule_init function. The context is also
543 returned to application in task callback functions, but this function
544 may be used to get it as well if needed. */
546 void *silc_schedule_get_context(SilcSchedule schedule)
548 return schedule->app_context;
551 /* Add new task to the scheduler */
553 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
554 SilcTaskCallback callback, void *context,
555 long seconds, long useconds,
558 SilcTask task = NULL;
560 if (!schedule->valid)
563 SILC_SCHEDULE_LOCK(schedule);
565 if (type == SILC_TASK_TIMEOUT) {
566 SilcTaskTimeout tmp, prev, ttask;
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 if ((seconds + useconds) > 0) {
583 silc_gettimeofday(&ttask->timeout);
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);