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 #ifdef SILC_DIST_INPLACE
250 /* Print schedule statistics to stdout */
252 void silc_schedule_stats(SilcSchedule schedule)
255 fprintf(stdout, "Schedule %p statistics:\n\n", schedule);
256 fprintf(stdout, "Num FD tasks : %lu (%lu bytes allocated)\n",
257 silc_hash_table_count(schedule->fd_queue),
258 sizeof(*ftask) * silc_hash_table_count(schedule->fd_queue));
259 fprintf(stdout, "Num Timeout tasks : %d (%d bytes allocated)\n",
260 silc_list_count(schedule->timeout_queue),
261 sizeof(struct SilcTaskTimeoutStruct) *
262 silc_list_count(schedule->timeout_queue));
263 fprintf(stdout, "Num Timeout freelist : %d (%d bytes allocated)\n",
264 silc_list_count(schedule->free_tasks),
265 sizeof(struct SilcTaskTimeoutStruct) *
266 silc_list_count(schedule->free_tasks));
268 #endif /* SILC_DIST_INPLACE */
270 /****************************** Public API **********************************/
272 /* Initializes the scheduler. This returns the scheduler context that
273 is given as arugment usually to all silc_schedule_* functions.
274 The `max_tasks' indicates the number of maximum tasks that the
275 scheduler can handle. The `app_context' is application specific
276 context that is delivered to task callbacks. */
278 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
280 SilcSchedule schedule;
282 SILC_LOG_DEBUG(("Initializing scheduler"));
284 schedule = silc_calloc(1, sizeof(*schedule));
289 silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
290 silc_schedule_fd_destructor, NULL, TRUE);
291 if (!schedule->fd_queue)
294 silc_list_init(schedule->timeout_queue, struct SilcTaskTimeoutStruct, next);
295 silc_list_init(schedule->free_tasks, struct SilcTaskTimeoutStruct, next);
297 schedule->app_context = app_context;
298 schedule->valid = TRUE;
299 schedule->max_tasks = max_tasks;
301 /* Allocate scheduler lock */
302 silc_mutex_alloc(&schedule->lock);
304 /* Initialize the platform specific scheduler. */
305 schedule->internal = schedule_ops.init(schedule, app_context);
310 /* Uninitializes the schedule. This is called when the program is ready
311 to end. This removes all tasks and task queues. Returns FALSE if the
312 scheduler could not be uninitialized. This happens when the scheduler
313 is still valid and silc_schedule_stop has not been called. */
315 SilcBool silc_schedule_uninit(SilcSchedule schedule)
319 SILC_LOG_DEBUG(("Uninitializing scheduler"));
321 if (schedule->valid == TRUE)
324 /* Dispatch all timeouts before going away */
325 SILC_SCHEDULE_LOCK(schedule);
326 silc_schedule_dispatch_timeout(schedule, TRUE);
327 SILC_SCHEDULE_UNLOCK(schedule);
329 /* Deliver signals before going away */
330 if (schedule->signal_tasks) {
331 schedule_ops.signals_call(schedule, schedule->internal);
332 schedule->signal_tasks = FALSE;
335 /* Unregister all tasks */
336 silc_schedule_task_del(schedule, SILC_ALL_TASKS);
337 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
339 /* Delete timeout task freelist */
340 silc_list_start(schedule->free_tasks);
341 while ((task = silc_list_get(schedule->free_tasks)))
344 /* Unregister all task queues */
345 silc_hash_table_free(schedule->fd_queue);
347 /* Uninit the platform specific scheduler. */
348 schedule_ops.uninit(schedule, schedule->internal);
350 silc_mutex_free(schedule->lock);
356 /* Stops the schedule even if it is not supposed to be stopped yet.
357 After calling this, one should call silc_schedule_uninit (after the
358 silc_schedule has returned). */
360 void silc_schedule_stop(SilcSchedule schedule)
362 SILC_LOG_DEBUG(("Stopping scheduler"));
363 SILC_SCHEDULE_LOCK(schedule);
364 schedule->valid = FALSE;
365 SILC_SCHEDULE_UNLOCK(schedule);
368 /* Runs the scheduler once and then returns. */
370 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
372 struct timeval timeout;
375 SILC_LOG_DEBUG(("In scheduler loop"));
377 if (!schedule->is_locked)
378 SILC_SCHEDULE_LOCK(schedule);
380 /* Deliver signals if any has been set to be called */
381 if (schedule->signal_tasks) {
382 SILC_SCHEDULE_UNLOCK(schedule);
383 schedule_ops.signals_call(schedule, schedule->internal);
384 schedule->signal_tasks = FALSE;
385 SILC_SCHEDULE_LOCK(schedule);
388 /* Check if scheduler is valid */
389 if (schedule->valid == FALSE) {
390 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
391 if (!schedule->is_locked)
392 SILC_SCHEDULE_UNLOCK(schedule);
396 /* Calculate next timeout for silc_select(). This is the timeout value
397 when at earliest some of the timeout tasks expire. This may dispatch
398 already expired timeouts. */
399 silc_schedule_select_timeout(schedule);
401 /* Check if scheduler is valid */
402 if (schedule->valid == FALSE) {
403 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
404 if (!schedule->is_locked)
405 SILC_SCHEDULE_UNLOCK(schedule);
409 if (timeout_usecs >= 0) {
411 timeout.tv_usec = timeout_usecs;
412 schedule->timeout = timeout;
413 schedule->has_timeout = TRUE;
416 /* This is the main silc_select(). The program blocks here until some
417 of the selected file descriptors change status or the selected
419 SILC_LOG_DEBUG(("Select"));
420 ret = schedule_ops.select(schedule, schedule->internal);
425 SILC_LOG_DEBUG(("Running timeout tasks"));
426 silc_schedule_dispatch_timeout(schedule, FALSE);
432 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
435 /* There is some data available now */
436 SILC_LOG_DEBUG(("Running fd tasks"));
437 silc_schedule_dispatch_fd(schedule);
441 if (!schedule->is_locked)
442 SILC_SCHEDULE_UNLOCK(schedule);
447 /* The SILC scheduler. This is actually the main routine in SILC programs.
448 When this returns the program is to be ended. Before this function can
449 be called, one must call silc_schedule_init function. */
451 void silc_schedule(SilcSchedule schedule)
453 SILC_LOG_DEBUG(("Running scheduler"));
455 if (schedule->valid == FALSE) {
456 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
460 SILC_SCHEDULE_LOCK(schedule);
461 schedule->is_locked = TRUE;
463 /* Start the scheduler loop */
464 while (silc_schedule_one(schedule, -1))
467 SILC_SCHEDULE_UNLOCK(schedule);
470 /* Wakes up the scheduler. This is used only in multi-threaded
471 environments where threads may add new tasks or remove old tasks
472 from task queues. This is called to wake up the scheduler in the
473 main thread so that it detects the changes in the task queues.
474 If threads support is not compiled in this function has no effect.
475 Implementation of this function is platform specific. */
477 void silc_schedule_wakeup(SilcSchedule schedule)
480 SILC_LOG_DEBUG(("Wakeup scheduler"));
481 SILC_SCHEDULE_LOCK(schedule);
482 schedule_ops.wakeup(schedule, schedule->internal);
483 SILC_SCHEDULE_UNLOCK(schedule);
487 /* Returns the application specific context that was saved into the
488 scheduler in silc_schedule_init function. The context is also
489 returned to application in task callback functions, but this function
490 may be used to get it as well if needed. */
492 void *silc_schedule_get_context(SilcSchedule schedule)
494 return schedule->app_context;
497 /* Add new task to the scheduler */
499 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
500 SilcTaskCallback callback, void *context,
501 long seconds, long useconds,
504 SilcTask task = NULL;
506 if (!schedule->valid)
509 SILC_SCHEDULE_LOCK(schedule);
511 if (type == SILC_TASK_TIMEOUT) {
512 SilcTaskTimeout tmp, prev, ttask;
514 ttask = silc_list_get(schedule->free_tasks);
516 ttask = silc_calloc(1, sizeof(*ttask));
520 silc_list_del(schedule->free_tasks, ttask);
522 ttask->header.type = 1;
523 ttask->header.callback = callback;
524 ttask->header.context = context;
525 ttask->header.valid = TRUE;
528 if ((seconds + useconds) > 0) {
529 silc_gettimeofday(&ttask->timeout);
530 ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
531 ttask->timeout.tv_usec += (useconds % 1000000L);
532 if (ttask->timeout.tv_usec >= 1000000L) {
533 ttask->timeout.tv_sec += 1;
534 ttask->timeout.tv_usec -= 1000000L;
538 SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
541 /* Add task to correct spot so that the first task in the list has
542 the earliest timeout. */
543 silc_list_start(schedule->timeout_queue);
545 while ((tmp = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
546 /* If we have shorter timeout, we have found our spot */
547 if (silc_compare_timeval(&ttask->timeout, &tmp->timeout)) {
548 silc_list_insert(schedule->timeout_queue, prev, ttask);
554 silc_list_add(schedule->timeout_queue, ttask);
556 task = (SilcTask)ttask;
558 } else if (type == SILC_TASK_FD) {
559 /* Check if fd is already added */
560 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
561 NULL, (void **)&task))
564 /* Check max tasks */
565 if (schedule->max_tasks > 0 &&
566 silc_hash_table_count(schedule->fd_queue) >= schedule->max_tasks) {
567 SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
571 SilcTaskFd ftask = silc_calloc(1, sizeof(*ftask));
575 SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
577 ftask->header.type = 0;
578 ftask->header.callback = callback;
579 ftask->header.context = context;
580 ftask->header.valid = TRUE;
581 ftask->events = SILC_TASK_READ;
585 silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask);
587 task = (SilcTask)ftask;
589 } else if (type == SILC_TASK_SIGNAL) {
590 SILC_SCHEDULE_UNLOCK(schedule);
591 schedule_ops.signal_register(schedule, schedule->internal, (int)fd,
598 SILC_SCHEDULE_UNLOCK(schedule);
602 /* Invalidates task */
604 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
606 if (task == SILC_ALL_TASKS) {
608 SilcHashTableList htl;
610 SILC_LOG_DEBUG(("Unregister all tasks"));
612 SILC_SCHEDULE_LOCK(schedule);
614 /* Delete from fd queue */
615 silc_hash_table_list(schedule->fd_queue, &htl);
616 while (silc_hash_table_get(&htl, NULL, (void **)&task))
618 silc_hash_table_list_reset(&htl);
620 /* Delete from timeout queue */
621 silc_list_start(schedule->timeout_queue);
622 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
626 SILC_SCHEDULE_UNLOCK(schedule);
630 SILC_LOG_DEBUG(("Unregistering task %p", task));
631 SILC_SCHEDULE_LOCK(schedule);
633 SILC_SCHEDULE_UNLOCK(schedule);
636 /* Invalidate task by fd */
638 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
640 SilcTask task = NULL;
642 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
644 SILC_SCHEDULE_LOCK(schedule);
646 /* fd is unique, so there is only one task with this fd in the table */
647 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd), NULL,
651 SILC_SCHEDULE_UNLOCK(schedule);
653 /* If it is signal, remove it */
655 schedule_ops.signal_unregister(schedule, schedule->internal, fd);
658 /* Invalidate task by task callback. */
660 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
661 SilcTaskCallback callback)
664 SilcHashTableList htl;
666 SILC_LOG_DEBUG(("Unregister task by callback"));
668 SILC_SCHEDULE_LOCK(schedule);
670 /* Delete from fd queue */
671 silc_hash_table_list(schedule->fd_queue, &htl);
672 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
673 if (task->callback == callback)
676 silc_hash_table_list_reset(&htl);
678 /* Delete from timeout queue */
679 silc_list_start(schedule->timeout_queue);
680 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
682 if (task->callback == callback)
686 SILC_SCHEDULE_UNLOCK(schedule);
689 /* Invalidate task by context. */
691 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
694 SilcHashTableList htl;
696 SILC_LOG_DEBUG(("Unregister task by context"));
698 SILC_SCHEDULE_LOCK(schedule);
700 /* Delete from fd queue */
701 silc_hash_table_list(schedule->fd_queue, &htl);
702 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
703 if (task->context == context)
706 silc_hash_table_list_reset(&htl);
708 /* Delete from timeout queue */
709 silc_list_start(schedule->timeout_queue);
710 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
712 if (task->context == context)
716 SILC_SCHEDULE_UNLOCK(schedule);
719 /* Invalidate task by all */
721 void silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
722 SilcTaskCallback callback, void *context)
726 SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
728 /* For fd task, callback and context is irrelevant as fd is unique */
730 silc_schedule_task_del_by_fd(schedule, fd);
732 SILC_SCHEDULE_LOCK(schedule);
734 /* Delete from timeout queue */
735 silc_list_start(schedule->timeout_queue);
736 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
738 if (task->callback == callback && task->context == context)
742 SILC_SCHEDULE_UNLOCK(schedule);
745 /* Sets a file descriptor to be listened by scheduler. One can call this
746 directly if wanted. This can be called multiple times for one file
747 descriptor to set different iomasks. */
749 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
750 SilcTaskEvent mask, SilcBool send_events)
754 if (!schedule->valid)
757 SILC_SCHEDULE_LOCK(schedule);
759 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
760 NULL, (void **)&task)) {
763 task->revents = mask;
764 silc_schedule_dispatch_fd(schedule);
768 SILC_SCHEDULE_UNLOCK(schedule);
771 /* Removes a file descriptor from listen list. */
773 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
775 silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);