5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 1998 - 2005 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.
21 #include "silcincludes.h"
23 /* Platform specific implementation */
24 extern const SilcScheduleOps schedule_ops;
26 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task);
27 static void silc_schedule_dispatch_fd(SilcSchedule schedule);
28 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
29 SilcBool dispatch_all);
31 /* Fd task hash table destructor */
33 static void silc_schedule_fd_destructor(void *key, void *context,
39 /* Initializes the scheduler. This returns the scheduler context that
40 is given as arugment usually to all silc_schedule_* functions.
41 The `max_tasks' indicates the number of maximum tasks that the
42 scheduler can handle. The `app_context' is application specific
43 context that is delivered to task callbacks. */
45 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
47 SilcSchedule schedule;
49 SILC_LOG_DEBUG(("Initializing scheduler"));
51 schedule = silc_calloc(1, sizeof(*schedule));
56 silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
57 silc_schedule_fd_destructor, NULL, TRUE);
58 if (!schedule->fd_queue)
61 silc_list_init(schedule->timeout_queue, struct SilcTaskTimeoutStruct, next);
63 schedule->app_context = app_context;
64 schedule->valid = TRUE;
65 schedule->max_tasks = max_tasks;
67 /* Allocate scheduler lock */
68 silc_mutex_alloc(&schedule->lock);
70 /* Initialize the platform specific scheduler. */
71 schedule->internal = schedule_ops.init(schedule, app_context);
76 /* Uninitializes the schedule. This is called when the program is ready
77 to end. This removes all tasks and task queues. Returns FALSE if the
78 scheduler could not be uninitialized. This happens when the scheduler
79 is still valid and silc_schedule_stop has not been called. */
81 SilcBool silc_schedule_uninit(SilcSchedule schedule)
83 SILC_LOG_DEBUG(("Uninitializing scheduler"));
85 if (schedule->valid == TRUE)
88 /* Dispatch all timeouts before going away */
89 SILC_SCHEDULE_LOCK(schedule);
90 silc_schedule_dispatch_timeout(schedule, TRUE);
91 SILC_SCHEDULE_UNLOCK(schedule);
93 /* Deliver signals before going away */
94 if (schedule->signal_tasks) {
95 schedule_ops.signals_call(schedule, schedule->internal);
96 schedule->signal_tasks = FALSE;
99 /* Unregister all tasks */
100 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
101 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
103 /* Unregister all task queues */
104 silc_hash_table_free(schedule->fd_queue);
106 /* Uninit the platform specific scheduler. */
107 schedule_ops.uninit(schedule, schedule->internal);
109 silc_mutex_free(schedule->lock);
115 /* Stops the schedule even if it is not supposed to be stopped yet.
116 After calling this, one should call silc_schedule_uninit (after the
117 silc_schedule has returned). */
119 void silc_schedule_stop(SilcSchedule schedule)
121 SILC_LOG_DEBUG(("Stopping scheduler"));
122 SILC_SCHEDULE_LOCK(schedule);
123 schedule->valid = FALSE;
124 SILC_SCHEDULE_UNLOCK(schedule);
127 /* Executes file descriptor tasks. Invalid tasks are removed here. */
129 static void silc_schedule_dispatch_fd(SilcSchedule schedule)
131 SilcHashTableList htl;
136 silc_hash_table_list(schedule->fd_queue, &htl);
137 while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task)) {
141 silc_schedule_task_remove(schedule, t);
144 if (!task->events || !task->revents)
147 /* Is the task ready for reading */
148 if (task->revents & SILC_TASK_READ) {
149 SILC_SCHEDULE_UNLOCK(schedule);
150 t->callback(schedule, schedule->app_context, SILC_TASK_READ,
151 task->fd, t->context);
152 SILC_SCHEDULE_LOCK(schedule);
155 /* Is the task ready for writing */
156 if (t->valid && task->revents & SILC_TASK_WRITE) {
157 SILC_SCHEDULE_UNLOCK(schedule);
158 t->callback(schedule, schedule->app_context, SILC_TASK_WRITE,
159 task->fd, t->context);
160 SILC_SCHEDULE_LOCK(schedule);
163 /* Remove if task was invalidated in the task callback */
165 silc_schedule_task_remove(schedule, t);
167 silc_hash_table_list_reset(&htl);
170 /* Executes all tasks whose timeout has expired. The task is removed from
171 the task queue after the callback function has returned. Also, invalid
172 tasks are removed here. */
174 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
175 SilcBool dispatch_all)
178 SilcTaskTimeout task;
179 struct timeval curtime;
182 SILC_LOG_DEBUG(("Running timeout tasks"));
184 silc_gettimeofday(&curtime);
186 /* First task in the task queue has always the earliest timeout. */
187 silc_list_start(schedule->timeout_queue);
188 while ((task = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
191 /* Remove invalid task */
193 silc_schedule_task_remove(schedule, t);
197 /* Execute the task if the timeout has expired */
198 if (dispatch_all || silc_compare_timeval(&task->timeout, &curtime)) {
199 SILC_SCHEDULE_UNLOCK(schedule);
200 t->callback(schedule, schedule->app_context, SILC_TASK_EXPIRE, 0,
202 SILC_SCHEDULE_LOCK(schedule);
204 /* Remove the expired task */
205 silc_schedule_task_remove(schedule, t);
207 /* Balance when we have lots of small timeouts */
214 /* Calculates next timeout. This is the timeout value when at earliest some
215 of the timeout tasks expire. If this is in the past, they will be
218 static void silc_schedule_select_timeout(SilcSchedule schedule)
221 SilcTaskTimeout task;
222 struct timeval curtime;
223 SilcBool dispatch = TRUE;
225 /* Get the current time */
226 silc_gettimeofday(&curtime);
227 schedule->has_timeout = FALSE;
229 /* First task in the task queue has always the earliest timeout. */
230 silc_list_start(schedule->timeout_queue);
231 while ((task = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
234 /* Remove invalid task */
236 silc_schedule_task_remove(schedule, t);
240 /* If the timeout is in past, we will run the task and all other
241 timeout tasks from the past. */
242 if (silc_compare_timeval(&task->timeout, &curtime) && dispatch) {
243 silc_schedule_dispatch_timeout(schedule, FALSE);
244 if (!schedule->valid)
247 /* Start selecting new timeout again after dispatch */
248 silc_list_start(schedule->timeout_queue);
253 /* Calculate the next timeout */
254 curtime.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
255 curtime.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
256 if (curtime.tv_sec < 0)
259 /* We wouldn't want to go under zero, check for it. */
260 if (curtime.tv_usec < 0) {
262 if (curtime.tv_sec < 0)
264 curtime.tv_usec += 1000000L;
270 /* Save the timeout */
272 schedule->timeout = curtime;
273 schedule->has_timeout = TRUE;
274 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout.tv_sec,
275 schedule->timeout.tv_usec));
279 /* Runs the scheduler once and then returns. */
281 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
283 struct timeval timeout;
286 SILC_LOG_DEBUG(("In scheduler loop"));
288 if (!schedule->is_locked)
289 SILC_SCHEDULE_LOCK(schedule);
291 /* Deliver signals if any has been set to be called */
292 if (schedule->signal_tasks) {
293 SILC_SCHEDULE_UNLOCK(schedule);
294 schedule_ops.signals_call(schedule, schedule->internal);
295 schedule->signal_tasks = FALSE;
296 SILC_SCHEDULE_LOCK(schedule);
299 /* Check if scheduler is valid */
300 if (schedule->valid == FALSE) {
301 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
302 if (!schedule->is_locked)
303 SILC_SCHEDULE_UNLOCK(schedule);
307 /* Calculate next timeout for silc_select(). This is the timeout value
308 when at earliest some of the timeout tasks expire. This may dispatch
309 already expired timeouts. */
310 silc_schedule_select_timeout(schedule);
312 /* Check if scheduler is valid */
313 if (schedule->valid == FALSE) {
314 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
315 if (!schedule->is_locked)
316 SILC_SCHEDULE_UNLOCK(schedule);
320 if (timeout_usecs >= 0) {
322 timeout.tv_usec = timeout_usecs;
323 schedule->timeout = timeout;
324 schedule->has_timeout = TRUE;
327 /* This is the main silc_select(). The program blocks here until some
328 of the selected file descriptors change status or the selected
330 SILC_LOG_DEBUG(("Select"));
331 ret = schedule_ops.select(schedule, schedule->internal);
338 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
342 SILC_LOG_DEBUG(("Running timeout tasks"));
343 silc_schedule_dispatch_timeout(schedule, FALSE);
346 /* There is some data available now */
347 SILC_LOG_DEBUG(("Running fd tasks"));
348 silc_schedule_dispatch_fd(schedule);
352 if (!schedule->is_locked)
353 SILC_SCHEDULE_UNLOCK(schedule);
358 /* The SILC scheduler. This is actually the main routine in SILC programs.
359 When this returns the program is to be ended. Before this function can
360 be called, one must call silc_schedule_init function. */
362 void silc_schedule(SilcSchedule schedule)
364 SILC_LOG_DEBUG(("Running scheduler"));
366 if (schedule->valid == FALSE) {
367 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
371 SILC_SCHEDULE_LOCK(schedule);
372 schedule->is_locked = TRUE;
374 /* Start the scheduler loop */
375 while (silc_schedule_one(schedule, -1))
378 SILC_SCHEDULE_UNLOCK(schedule);
381 /* Wakes up the scheduler. This is used only in multi-threaded
382 environments where threads may add new tasks or remove old tasks
383 from task queues. This is called to wake up the scheduler in the
384 main thread so that it detects the changes in the task queues.
385 If threads support is not compiled in this function has no effect.
386 Implementation of this function is platform specific. */
388 void silc_schedule_wakeup(SilcSchedule schedule)
391 SILC_LOG_DEBUG(("Wakeup scheduler"));
392 SILC_SCHEDULE_LOCK(schedule);
393 schedule_ops.wakeup(schedule, schedule->internal);
394 SILC_SCHEDULE_UNLOCK(schedule);
398 /* Returns the application specific context that was saved into the
399 scheduler in silc_schedule_init function. The context is also
400 returned to application in task callback functions, but this function
401 may be used to get it as well if needed. */
403 void *silc_schedule_get_context(SilcSchedule schedule)
405 return schedule->app_context;
408 /* Add new task to the scheduler */
410 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
411 SilcTaskCallback callback, void *context,
412 long seconds, long useconds,
415 SilcTask task = NULL;
417 if (!schedule->valid)
420 SILC_SCHEDULE_LOCK(schedule);
422 if (type == SILC_TASK_TIMEOUT) {
423 SilcTaskTimeout tmp, prev, ttask = silc_calloc(1, sizeof(*ttask));
427 SILC_LOG_DEBUG(("Registering new timeout task %p", ttask));
429 ttask->header.type = 1;
430 ttask->header.callback = callback;
431 ttask->header.context = context;
432 ttask->header.valid = TRUE;
435 if ((seconds + useconds) > 0) {
436 silc_gettimeofday(&ttask->timeout);
437 ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
438 ttask->timeout.tv_usec += (useconds % 1000000L);
439 if (ttask->timeout.tv_usec > 999999L) {
440 ttask->timeout.tv_sec += 1;
441 ttask->timeout.tv_usec -= 1000000L;
445 /* Add task to correct spot so that the first task in the list has
446 the earliest timeout. */
447 silc_list_start(schedule->timeout_queue);
449 while ((tmp = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
450 /* If we have shorter timeout, we have found our spot */
451 if (silc_compare_timeval(&ttask->timeout, &tmp->timeout)) {
452 silc_list_insert(schedule->timeout_queue, prev, ttask);
458 silc_list_add(schedule->timeout_queue, ttask);
460 task = (SilcTask)ttask;
462 /* Check if fd is already added */
463 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
467 /* Check max tasks */
468 if (schedule->max_tasks > 0 &&
469 silc_hash_table_count(schedule->fd_queue) >= schedule->max_tasks) {
470 SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
474 SilcTaskFd ftask = silc_calloc(1, sizeof(*ftask));
478 SILC_LOG_DEBUG(("Registering new fd task %p fd=%d", ftask, fd));
480 ftask->header.type = 0;
481 ftask->header.callback = callback;
482 ftask->header.context = context;
483 ftask->header.valid = TRUE;
484 ftask->events = SILC_TASK_READ;
488 silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask);
490 task = (SilcTask)ftask;
494 SILC_SCHEDULE_UNLOCK(schedule);
498 /* Invalidates task */
500 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
502 if (task == SILC_ALL_TASKS) {
504 SilcHashTableList htl;
506 SILC_LOG_DEBUG(("Unregister all tasks"));
508 SILC_SCHEDULE_LOCK(schedule);
510 /* Delete from fd queue */
511 silc_hash_table_list(schedule->fd_queue, &htl);
512 while (silc_hash_table_get(&htl, NULL, (void **)&task))
514 silc_hash_table_list_reset(&htl);
516 /* Delete from timeout queue */
517 silc_list_start(schedule->timeout_queue);
518 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
522 SILC_SCHEDULE_UNLOCK(schedule);
526 SILC_LOG_DEBUG(("Unregistering task %p", task));
527 SILC_SCHEDULE_LOCK(schedule);
529 SILC_SCHEDULE_UNLOCK(schedule);
532 /* Invalidate task by fd */
534 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
538 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
540 SILC_SCHEDULE_LOCK(schedule);
542 /* fd is unique, so there is only one task with this fd in the table */
543 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd), NULL,
547 SILC_SCHEDULE_UNLOCK(schedule);
550 /* Invalidate task by task callback. */
552 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
553 SilcTaskCallback callback)
556 SilcHashTableList htl;
558 SILC_LOG_DEBUG(("Unregister task by callback"));
560 SILC_SCHEDULE_LOCK(schedule);
562 /* Delete from fd queue */
563 silc_hash_table_list(schedule->fd_queue, &htl);
564 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
565 if (task->callback == callback)
568 silc_hash_table_list_reset(&htl);
570 /* Delete from timeout queue */
571 silc_list_start(schedule->timeout_queue);
572 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
574 if (task->callback == callback)
578 SILC_SCHEDULE_UNLOCK(schedule);
581 /* Invalidate task by context. */
583 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
586 SilcHashTableList htl;
588 SILC_LOG_DEBUG(("Unregister task by context"));
590 SILC_SCHEDULE_LOCK(schedule);
592 /* Delete from fd queue */
593 silc_hash_table_list(schedule->fd_queue, &htl);
594 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
595 if (task->context == context)
598 silc_hash_table_list_reset(&htl);
600 /* Delete from timeout queue */
601 silc_list_start(schedule->timeout_queue);
602 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
604 if (task->context == context)
608 SILC_SCHEDULE_UNLOCK(schedule);
611 /* Invalidate task by all */
613 void silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
614 SilcTaskCallback callback, void *context)
618 SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
620 /* For fd task, callback and context is irrelevant as fd is unique */
622 silc_schedule_task_del_by_fd(schedule, fd);
624 SILC_SCHEDULE_LOCK(schedule);
626 /* Delete from timeout queue */
627 silc_list_start(schedule->timeout_queue);
628 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
630 if (task->callback == callback && task->context == context)
634 SILC_SCHEDULE_UNLOCK(schedule);
637 /* Removes task from the scheduler. This must be called with scheduler
640 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task)
643 SilcTaskTimeout ttask;
645 if (task == SILC_ALL_TASKS) {
647 SilcHashTableList htl;
650 /* Delete from fd queue */
651 silc_hash_table_list(schedule->fd_queue, &htl);
652 while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task))
653 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd));
654 silc_hash_table_list_reset(&htl);
656 /* Delete from timeout queue */
657 silc_list_start(schedule->timeout_queue);
658 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
660 silc_list_del(schedule->timeout_queue, task);
667 /* Delete from timeout queue */
668 if (task->type == 1) {
669 silc_list_start(schedule->timeout_queue);
670 while ((ttask = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
671 if (ttask == (SilcTaskTimeout)task) {
672 silc_list_del(schedule->timeout_queue, ttask);
681 /* Delete from fd queue */
682 ftask = (SilcTaskFd)task;
683 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd));
686 /* Sets a file descriptor to be listened by scheduler. One can call this
687 directly if wanted. This can be called multiple times for one file
688 descriptor to set different iomasks. */
690 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
691 SilcTaskEvent mask, SilcBool send_events)
695 if (!schedule->valid)
698 SILC_SCHEDULE_LOCK(schedule);
700 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
701 NULL, (void **)&task)) {
704 task->revents = mask;
705 silc_schedule_dispatch_fd(schedule);
709 SILC_SCHEDULE_UNLOCK(schedule);
712 /* Removes a file descriptor from listen list. */
714 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
716 silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);
719 /* Register a new signal */
721 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
722 SilcTaskCallback callback, void *context)
724 schedule_ops.signal_register(schedule, schedule->internal, signal,
728 /* Unregister a new signal */
730 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
731 SilcTaskCallback callback, void *context)
733 schedule_ops.signal_unregister(schedule, schedule->internal, signal,
737 /* Call signal indicated by `signal'. */
739 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
741 /* Mark that signals needs to be delivered later. */
742 schedule_ops.signal_call(schedule, schedule->internal, signal);
743 schedule->signal_tasks = TRUE;