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 = (SilcTask)silc_list_get(schedule->timeout_queue))
220 silc_list_del(schedule->timeout_queue, task);
227 /* Delete from timeout queue */
228 if (task->type == 1) {
229 silc_list_start(schedule->timeout_queue);
230 while ((ttask = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
231 if (ttask == (SilcTaskTimeout)task) {
232 silc_list_del(schedule->timeout_queue, ttask);
241 /* Delete from fd queue */
242 ftask = (SilcTaskFd)task;
243 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd));
247 /****************************** Public API **********************************/
249 /* Initializes the scheduler. This returns the scheduler context that
250 is given as arugment usually to all silc_schedule_* functions.
251 The `max_tasks' indicates the number of maximum tasks that the
252 scheduler can handle. The `app_context' is application specific
253 context that is delivered to task callbacks. */
255 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
257 SilcSchedule schedule;
259 SILC_LOG_DEBUG(("Initializing scheduler"));
261 schedule = silc_calloc(1, sizeof(*schedule));
266 silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
267 silc_schedule_fd_destructor, NULL, TRUE);
268 if (!schedule->fd_queue)
271 silc_list_init(schedule->timeout_queue, struct SilcTaskTimeoutStruct, next);
273 schedule->app_context = app_context;
274 schedule->valid = TRUE;
275 schedule->max_tasks = max_tasks;
277 /* Allocate scheduler lock */
278 silc_mutex_alloc(&schedule->lock);
280 /* Initialize the platform specific scheduler. */
281 schedule->internal = schedule_ops.init(schedule, app_context);
286 /* Uninitializes the schedule. This is called when the program is ready
287 to end. This removes all tasks and task queues. Returns FALSE if the
288 scheduler could not be uninitialized. This happens when the scheduler
289 is still valid and silc_schedule_stop has not been called. */
291 SilcBool silc_schedule_uninit(SilcSchedule schedule)
293 SILC_LOG_DEBUG(("Uninitializing scheduler"));
295 if (schedule->valid == TRUE)
298 /* Dispatch all timeouts before going away */
299 SILC_SCHEDULE_LOCK(schedule);
300 silc_schedule_dispatch_timeout(schedule, TRUE);
301 SILC_SCHEDULE_UNLOCK(schedule);
303 /* Deliver signals before going away */
304 if (schedule->signal_tasks) {
305 schedule_ops.signals_call(schedule, schedule->internal);
306 schedule->signal_tasks = FALSE;
309 /* Unregister all tasks */
310 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
311 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
313 /* Unregister all task queues */
314 silc_hash_table_free(schedule->fd_queue);
316 /* Uninit the platform specific scheduler. */
317 schedule_ops.uninit(schedule, schedule->internal);
319 silc_mutex_free(schedule->lock);
325 /* Stops the schedule even if it is not supposed to be stopped yet.
326 After calling this, one should call silc_schedule_uninit (after the
327 silc_schedule has returned). */
329 void silc_schedule_stop(SilcSchedule schedule)
331 SILC_LOG_DEBUG(("Stopping scheduler"));
332 SILC_SCHEDULE_LOCK(schedule);
333 schedule->valid = FALSE;
334 SILC_SCHEDULE_UNLOCK(schedule);
337 /* Runs the scheduler once and then returns. */
339 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
341 struct timeval timeout;
344 SILC_LOG_DEBUG(("In scheduler loop"));
346 if (!schedule->is_locked)
347 SILC_SCHEDULE_LOCK(schedule);
349 /* Deliver signals if any has been set to be called */
350 if (schedule->signal_tasks) {
351 SILC_SCHEDULE_UNLOCK(schedule);
352 schedule_ops.signals_call(schedule, schedule->internal);
353 schedule->signal_tasks = FALSE;
354 SILC_SCHEDULE_LOCK(schedule);
357 /* Check if scheduler is valid */
358 if (schedule->valid == FALSE) {
359 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
360 if (!schedule->is_locked)
361 SILC_SCHEDULE_UNLOCK(schedule);
365 /* Calculate next timeout for silc_select(). This is the timeout value
366 when at earliest some of the timeout tasks expire. This may dispatch
367 already expired timeouts. */
368 silc_schedule_select_timeout(schedule);
370 /* Check if scheduler is valid */
371 if (schedule->valid == FALSE) {
372 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
373 if (!schedule->is_locked)
374 SILC_SCHEDULE_UNLOCK(schedule);
378 if (timeout_usecs >= 0) {
380 timeout.tv_usec = timeout_usecs;
381 schedule->timeout = timeout;
382 schedule->has_timeout = TRUE;
385 /* This is the main silc_select(). The program blocks here until some
386 of the selected file descriptors change status or the selected
388 SILC_LOG_DEBUG(("Select"));
389 ret = schedule_ops.select(schedule, schedule->internal);
394 SILC_LOG_DEBUG(("Running timeout tasks"));
395 silc_schedule_dispatch_timeout(schedule, FALSE);
401 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
404 /* There is some data available now */
405 SILC_LOG_DEBUG(("Running fd tasks"));
406 silc_schedule_dispatch_fd(schedule);
410 if (!schedule->is_locked)
411 SILC_SCHEDULE_UNLOCK(schedule);
416 /* The SILC scheduler. This is actually the main routine in SILC programs.
417 When this returns the program is to be ended. Before this function can
418 be called, one must call silc_schedule_init function. */
420 void silc_schedule(SilcSchedule schedule)
422 SILC_LOG_DEBUG(("Running scheduler"));
424 if (schedule->valid == FALSE) {
425 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
429 SILC_SCHEDULE_LOCK(schedule);
430 schedule->is_locked = TRUE;
432 /* Start the scheduler loop */
433 while (silc_schedule_one(schedule, -1))
436 SILC_SCHEDULE_UNLOCK(schedule);
439 /* Wakes up the scheduler. This is used only in multi-threaded
440 environments where threads may add new tasks or remove old tasks
441 from task queues. This is called to wake up the scheduler in the
442 main thread so that it detects the changes in the task queues.
443 If threads support is not compiled in this function has no effect.
444 Implementation of this function is platform specific. */
446 void silc_schedule_wakeup(SilcSchedule schedule)
449 SILC_LOG_DEBUG(("Wakeup scheduler"));
450 SILC_SCHEDULE_LOCK(schedule);
451 schedule_ops.wakeup(schedule, schedule->internal);
452 SILC_SCHEDULE_UNLOCK(schedule);
456 /* Returns the application specific context that was saved into the
457 scheduler in silc_schedule_init function. The context is also
458 returned to application in task callback functions, but this function
459 may be used to get it as well if needed. */
461 void *silc_schedule_get_context(SilcSchedule schedule)
463 return schedule->app_context;
466 /* Add new task to the scheduler */
468 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
469 SilcTaskCallback callback, void *context,
470 long seconds, long useconds,
473 SilcTask task = NULL;
475 if (!schedule->valid)
478 SILC_SCHEDULE_LOCK(schedule);
480 if (type == SILC_TASK_TIMEOUT) {
481 SilcTaskTimeout tmp, prev, ttask = silc_calloc(1, sizeof(*ttask));
485 ttask->header.type = 1;
486 ttask->header.callback = callback;
487 ttask->header.context = context;
488 ttask->header.valid = TRUE;
491 if ((seconds + useconds) > 0) {
492 silc_gettimeofday(&ttask->timeout);
493 ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
494 ttask->timeout.tv_usec += (useconds % 1000000L);
495 if (ttask->timeout.tv_usec >= 1000000L) {
496 ttask->timeout.tv_sec += 1;
497 ttask->timeout.tv_usec -= 1000000L;
501 SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
504 /* Add task to correct spot so that the first task in the list has
505 the earliest timeout. */
506 silc_list_start(schedule->timeout_queue);
508 while ((tmp = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
509 /* If we have shorter timeout, we have found our spot */
510 if (silc_compare_timeval(&ttask->timeout, &tmp->timeout)) {
511 silc_list_insert(schedule->timeout_queue, prev, ttask);
517 silc_list_add(schedule->timeout_queue, ttask);
519 task = (SilcTask)ttask;
521 /* Check if fd is already added */
522 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
523 NULL, (void **)&task))
526 /* Check max tasks */
527 if (schedule->max_tasks > 0 &&
528 silc_hash_table_count(schedule->fd_queue) >= schedule->max_tasks) {
529 SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
533 SilcTaskFd ftask = silc_calloc(1, sizeof(*ftask));
537 SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
539 ftask->header.type = 0;
540 ftask->header.callback = callback;
541 ftask->header.context = context;
542 ftask->header.valid = TRUE;
543 ftask->events = SILC_TASK_READ;
547 silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask);
549 task = (SilcTask)ftask;
553 SILC_SCHEDULE_UNLOCK(schedule);
557 /* Invalidates task */
559 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
561 if (task == SILC_ALL_TASKS) {
563 SilcHashTableList htl;
565 SILC_LOG_DEBUG(("Unregister all tasks"));
567 SILC_SCHEDULE_LOCK(schedule);
569 /* Delete from fd queue */
570 silc_hash_table_list(schedule->fd_queue, &htl);
571 while (silc_hash_table_get(&htl, NULL, (void **)&task))
573 silc_hash_table_list_reset(&htl);
575 /* Delete from timeout queue */
576 silc_list_start(schedule->timeout_queue);
577 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
581 SILC_SCHEDULE_UNLOCK(schedule);
585 SILC_LOG_DEBUG(("Unregistering task %p", task));
586 SILC_SCHEDULE_LOCK(schedule);
588 SILC_SCHEDULE_UNLOCK(schedule);
591 /* Invalidate task by fd */
593 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
597 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
599 SILC_SCHEDULE_LOCK(schedule);
601 /* fd is unique, so there is only one task with this fd in the table */
602 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd), NULL,
606 SILC_SCHEDULE_UNLOCK(schedule);
609 /* Invalidate task by task callback. */
611 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
612 SilcTaskCallback callback)
615 SilcHashTableList htl;
617 SILC_LOG_DEBUG(("Unregister task by callback"));
619 SILC_SCHEDULE_LOCK(schedule);
621 /* Delete from fd queue */
622 silc_hash_table_list(schedule->fd_queue, &htl);
623 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
624 if (task->callback == callback)
627 silc_hash_table_list_reset(&htl);
629 /* Delete from timeout queue */
630 silc_list_start(schedule->timeout_queue);
631 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
633 if (task->callback == callback)
637 SILC_SCHEDULE_UNLOCK(schedule);
640 /* Invalidate task by context. */
642 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
645 SilcHashTableList htl;
647 SILC_LOG_DEBUG(("Unregister task by context"));
649 SILC_SCHEDULE_LOCK(schedule);
651 /* Delete from fd queue */
652 silc_hash_table_list(schedule->fd_queue, &htl);
653 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
654 if (task->context == context)
657 silc_hash_table_list_reset(&htl);
659 /* Delete from timeout queue */
660 silc_list_start(schedule->timeout_queue);
661 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
663 if (task->context == context)
667 SILC_SCHEDULE_UNLOCK(schedule);
670 /* Invalidate task by all */
672 void silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
673 SilcTaskCallback callback, void *context)
677 SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
679 /* For fd task, callback and context is irrelevant as fd is unique */
681 silc_schedule_task_del_by_fd(schedule, fd);
683 SILC_SCHEDULE_LOCK(schedule);
685 /* Delete from timeout queue */
686 silc_list_start(schedule->timeout_queue);
687 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
689 if (task->callback == callback && task->context == context)
693 SILC_SCHEDULE_UNLOCK(schedule);
696 /* Sets a file descriptor to be listened by scheduler. One can call this
697 directly if wanted. This can be called multiple times for one file
698 descriptor to set different iomasks. */
700 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
701 SilcTaskEvent mask, SilcBool send_events)
705 if (!schedule->valid)
708 SILC_SCHEDULE_LOCK(schedule);
710 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
711 NULL, (void **)&task)) {
714 task->revents = mask;
715 silc_schedule_dispatch_fd(schedule);
719 SILC_SCHEDULE_UNLOCK(schedule);
722 /* Removes a file descriptor from listen list. */
724 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
726 silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);
729 /* Register a new signal */
731 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
732 SilcTaskCallback callback, void *context)
734 schedule_ops.signal_register(schedule, schedule->internal, signal,
738 /* Unregister a new signal */
740 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
741 SilcTaskCallback callback, void *context)
743 schedule_ops.signal_unregister(schedule, schedule->internal, signal,
747 /* Call signal indicated by `signal'. */
749 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
751 /* Mark that signals needs to be delivered later. */
752 schedule_ops.signal_call(schedule, schedule->internal, signal);
753 schedule->signal_tasks = TRUE;