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 /* 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)) {
200 SILC_SCHEDULE_UNLOCK(schedule);
201 t->callback(schedule, schedule->app_context, SILC_TASK_EXPIRE, 0,
203 SILC_SCHEDULE_LOCK(schedule);
205 /* Remove the expired task */
206 silc_schedule_task_remove(schedule, t);
208 /* Balance when we have lots of small timeouts */
215 /* Calculates next timeout. This is the timeout value when at earliest some
216 of the timeout tasks expire. If this is in the past, they will be
219 static void silc_schedule_select_timeout(SilcSchedule schedule)
222 SilcTaskTimeout task;
223 struct timeval curtime;
224 SilcBool dispatch = TRUE;
226 /* Get the current time */
227 silc_gettimeofday(&curtime);
228 schedule->has_timeout = FALSE;
230 /* First task in the task queue has always the earliest timeout. */
231 silc_list_start(schedule->timeout_queue);
232 while ((task = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
235 /* Remove invalid task */
237 silc_schedule_task_remove(schedule, t);
241 /* If the timeout is in past, we will run the task and all other
242 timeout tasks from the past. */
243 if (silc_compare_timeval(&task->timeout, &curtime) && dispatch) {
244 silc_schedule_dispatch_timeout(schedule, FALSE);
245 if (!schedule->valid)
248 /* Start selecting new timeout again after dispatch */
249 silc_list_start(schedule->timeout_queue);
254 /* Calculate the next timeout */
255 curtime.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
256 curtime.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
257 if (curtime.tv_sec < 0)
260 /* We wouldn't want to go under zero, check for it. */
261 if (curtime.tv_usec < 0) {
263 if (curtime.tv_sec < 0)
265 curtime.tv_usec += 1000000L;
271 /* Save the timeout */
273 schedule->timeout = curtime;
274 schedule->has_timeout = TRUE;
275 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout.tv_sec,
276 schedule->timeout.tv_usec));
280 /* Runs the scheduler once and then returns. */
282 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
284 struct timeval timeout;
287 SILC_LOG_DEBUG(("In scheduler loop"));
289 if (!schedule->is_locked)
290 SILC_SCHEDULE_LOCK(schedule);
292 /* Deliver signals if any has been set to be called */
293 if (schedule->signal_tasks) {
294 SILC_SCHEDULE_UNLOCK(schedule);
295 schedule_ops.signals_call(schedule, schedule->internal);
296 schedule->signal_tasks = FALSE;
297 SILC_SCHEDULE_LOCK(schedule);
300 /* Check if scheduler is valid */
301 if (schedule->valid == FALSE) {
302 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
303 if (!schedule->is_locked)
304 SILC_SCHEDULE_UNLOCK(schedule);
308 /* Calculate next timeout for silc_select(). This is the timeout value
309 when at earliest some of the timeout tasks expire. This may dispatch
310 already expired timeouts. */
311 silc_schedule_select_timeout(schedule);
313 /* Check if scheduler is valid */
314 if (schedule->valid == FALSE) {
315 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
316 if (!schedule->is_locked)
317 SILC_SCHEDULE_UNLOCK(schedule);
321 if (timeout_usecs >= 0) {
323 timeout.tv_usec = timeout_usecs;
324 schedule->timeout = timeout;
325 schedule->has_timeout = TRUE;
328 /* This is the main silc_select(). The program blocks here until some
329 of the selected file descriptors change status or the selected
331 SILC_LOG_DEBUG(("Select"));
332 ret = schedule_ops.select(schedule, schedule->internal);
337 SILC_LOG_DEBUG(("Running timeout tasks"));
338 silc_schedule_dispatch_timeout(schedule, FALSE);
344 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
347 /* There is some data available now */
348 SILC_LOG_DEBUG(("Running fd tasks"));
349 silc_schedule_dispatch_fd(schedule);
353 if (!schedule->is_locked)
354 SILC_SCHEDULE_UNLOCK(schedule);
359 /* The SILC scheduler. This is actually the main routine in SILC programs.
360 When this returns the program is to be ended. Before this function can
361 be called, one must call silc_schedule_init function. */
363 void silc_schedule(SilcSchedule schedule)
365 SILC_LOG_DEBUG(("Running scheduler"));
367 if (schedule->valid == FALSE) {
368 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
372 SILC_SCHEDULE_LOCK(schedule);
373 schedule->is_locked = TRUE;
375 /* Start the scheduler loop */
376 while (silc_schedule_one(schedule, -1))
379 SILC_SCHEDULE_UNLOCK(schedule);
382 /* Wakes up the scheduler. This is used only in multi-threaded
383 environments where threads may add new tasks or remove old tasks
384 from task queues. This is called to wake up the scheduler in the
385 main thread so that it detects the changes in the task queues.
386 If threads support is not compiled in this function has no effect.
387 Implementation of this function is platform specific. */
389 void silc_schedule_wakeup(SilcSchedule schedule)
392 SILC_LOG_DEBUG(("Wakeup scheduler"));
393 SILC_SCHEDULE_LOCK(schedule);
394 schedule_ops.wakeup(schedule, schedule->internal);
395 SILC_SCHEDULE_UNLOCK(schedule);
399 /* Returns the application specific context that was saved into the
400 scheduler in silc_schedule_init function. The context is also
401 returned to application in task callback functions, but this function
402 may be used to get it as well if needed. */
404 void *silc_schedule_get_context(SilcSchedule schedule)
406 return schedule->app_context;
409 /* Add new task to the scheduler */
411 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
412 SilcTaskCallback callback, void *context,
413 long seconds, long useconds,
416 SilcTask task = NULL;
418 if (!schedule->valid)
421 SILC_SCHEDULE_LOCK(schedule);
423 if (type == SILC_TASK_TIMEOUT) {
424 SilcTaskTimeout tmp, prev, ttask = silc_calloc(1, sizeof(*ttask));
428 ttask->header.type = 1;
429 ttask->header.callback = callback;
430 ttask->header.context = context;
431 ttask->header.valid = TRUE;
434 if ((seconds + useconds) > 0) {
435 silc_gettimeofday(&ttask->timeout);
436 ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
437 ttask->timeout.tv_usec += (useconds % 1000000L);
438 if (ttask->timeout.tv_usec >= 1000000L) {
439 ttask->timeout.tv_sec += 1;
440 ttask->timeout.tv_usec -= 1000000L;
444 SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
447 /* Add task to correct spot so that the first task in the list has
448 the earliest timeout. */
449 silc_list_start(schedule->timeout_queue);
451 while ((tmp = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
452 /* If we have shorter timeout, we have found our spot */
453 if (silc_compare_timeval(&ttask->timeout, &tmp->timeout)) {
454 silc_list_insert(schedule->timeout_queue, prev, ttask);
460 silc_list_add(schedule->timeout_queue, ttask);
462 task = (SilcTask)ttask;
464 /* Check if fd is already added */
465 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
466 NULL, (void **)&task))
469 /* Check max tasks */
470 if (schedule->max_tasks > 0 &&
471 silc_hash_table_count(schedule->fd_queue) >= schedule->max_tasks) {
472 SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
476 SilcTaskFd ftask = silc_calloc(1, sizeof(*ftask));
480 SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
482 ftask->header.type = 0;
483 ftask->header.callback = callback;
484 ftask->header.context = context;
485 ftask->header.valid = TRUE;
486 ftask->events = SILC_TASK_READ;
490 silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask);
492 task = (SilcTask)ftask;
496 SILC_SCHEDULE_UNLOCK(schedule);
500 /* Invalidates task */
502 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
504 if (task == SILC_ALL_TASKS) {
506 SilcHashTableList htl;
508 SILC_LOG_DEBUG(("Unregister all tasks"));
510 SILC_SCHEDULE_LOCK(schedule);
512 /* Delete from fd queue */
513 silc_hash_table_list(schedule->fd_queue, &htl);
514 while (silc_hash_table_get(&htl, NULL, (void **)&task))
516 silc_hash_table_list_reset(&htl);
518 /* Delete from timeout queue */
519 silc_list_start(schedule->timeout_queue);
520 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
524 SILC_SCHEDULE_UNLOCK(schedule);
528 SILC_LOG_DEBUG(("Unregistering task %p", task));
529 SILC_SCHEDULE_LOCK(schedule);
531 SILC_SCHEDULE_UNLOCK(schedule);
534 /* Invalidate task by fd */
536 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
540 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
542 SILC_SCHEDULE_LOCK(schedule);
544 /* fd is unique, so there is only one task with this fd in the table */
545 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd), NULL,
549 SILC_SCHEDULE_UNLOCK(schedule);
552 /* Invalidate task by task callback. */
554 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
555 SilcTaskCallback callback)
558 SilcHashTableList htl;
560 SILC_LOG_DEBUG(("Unregister task by callback"));
562 SILC_SCHEDULE_LOCK(schedule);
564 /* Delete from fd queue */
565 silc_hash_table_list(schedule->fd_queue, &htl);
566 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
567 if (task->callback == callback)
570 silc_hash_table_list_reset(&htl);
572 /* Delete from timeout queue */
573 silc_list_start(schedule->timeout_queue);
574 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
576 if (task->callback == callback)
580 SILC_SCHEDULE_UNLOCK(schedule);
583 /* Invalidate task by context. */
585 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
588 SilcHashTableList htl;
590 SILC_LOG_DEBUG(("Unregister task by context"));
592 SILC_SCHEDULE_LOCK(schedule);
594 /* Delete from fd queue */
595 silc_hash_table_list(schedule->fd_queue, &htl);
596 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
597 if (task->context == context)
600 silc_hash_table_list_reset(&htl);
602 /* Delete from timeout queue */
603 silc_list_start(schedule->timeout_queue);
604 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
606 if (task->context == context)
610 SILC_SCHEDULE_UNLOCK(schedule);
613 /* Invalidate task by all */
615 void silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
616 SilcTaskCallback callback, void *context)
620 SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
622 /* For fd task, callback and context is irrelevant as fd is unique */
624 silc_schedule_task_del_by_fd(schedule, fd);
626 SILC_SCHEDULE_LOCK(schedule);
628 /* Delete from timeout queue */
629 silc_list_start(schedule->timeout_queue);
630 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
632 if (task->callback == callback && task->context == context)
636 SILC_SCHEDULE_UNLOCK(schedule);
639 /* Removes task from the scheduler. This must be called with scheduler
642 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task)
645 SilcTaskTimeout ttask;
647 if (task == SILC_ALL_TASKS) {
649 SilcHashTableList htl;
652 /* Delete from fd queue */
653 silc_hash_table_list(schedule->fd_queue, &htl);
654 while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task))
655 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd));
656 silc_hash_table_list_reset(&htl);
658 /* Delete from timeout queue */
659 silc_list_start(schedule->timeout_queue);
660 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
662 silc_list_del(schedule->timeout_queue, task);
669 /* Delete from timeout queue */
670 if (task->type == 1) {
671 silc_list_start(schedule->timeout_queue);
672 while ((ttask = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
673 if (ttask == (SilcTaskTimeout)task) {
674 silc_list_del(schedule->timeout_queue, ttask);
683 /* Delete from fd queue */
684 ftask = (SilcTaskFd)task;
685 silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd));
688 /* Sets a file descriptor to be listened by scheduler. One can call this
689 directly if wanted. This can be called multiple times for one file
690 descriptor to set different iomasks. */
692 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
693 SilcTaskEvent mask, SilcBool send_events)
697 if (!schedule->valid)
700 SILC_SCHEDULE_LOCK(schedule);
702 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
703 NULL, (void **)&task)) {
706 task->revents = mask;
707 silc_schedule_dispatch_fd(schedule);
711 SILC_SCHEDULE_UNLOCK(schedule);
714 /* Removes a file descriptor from listen list. */
716 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
718 silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);
721 /* Register a new signal */
723 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
724 SilcTaskCallback callback, void *context)
726 schedule_ops.signal_register(schedule, schedule->internal, signal,
730 /* Unregister a new signal */
732 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
733 SilcTaskCallback callback, void *context)
735 schedule_ops.signal_unregister(schedule, schedule->internal, signal,
739 /* Call signal indicated by `signal'. */
741 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
743 /* Mark that signals needs to be delivered later. */
744 schedule_ops.signal_call(schedule, schedule->internal, signal);
745 schedule->signal_tasks = TRUE;