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 /* Timeout freelist garbage collection */
251 SILC_TASK_CALLBACK(silc_schedule_timeout_gc)
256 if (!schedule->valid)
259 SILC_LOG_DEBUG(("Timeout freelist garbage collection"));
261 SILC_SCHEDULE_LOCK(schedule);
263 if (silc_list_count(schedule->free_tasks) <= 10) {
264 SILC_SCHEDULE_UNLOCK(schedule);
265 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
269 if (silc_list_count(schedule->timeout_queue) >
270 silc_list_count(schedule->free_tasks)) {
271 SILC_SCHEDULE_UNLOCK(schedule);
272 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
277 c = silc_list_count(schedule->free_tasks) / 2;
278 if (c > silc_list_count(schedule->timeout_queue))
279 c = (silc_list_count(schedule->free_tasks) -
280 silc_list_count(schedule->timeout_queue));
281 if (silc_list_count(schedule->free_tasks) - c < 10)
282 c -= (10 - (silc_list_count(schedule->free_tasks) - c));
284 SILC_LOG_DEBUG(("Freeing %d unused tasks, leaving %d", c,
285 silc_list_count(schedule->free_tasks) - c));
287 silc_list_start(schedule->free_tasks);
288 while ((t = silc_list_get(schedule->free_tasks)) && c-- > 0) {
289 silc_list_del(schedule->free_tasks, t);
292 silc_list_start(schedule->free_tasks);
294 SILC_SCHEDULE_UNLOCK(schedule);
296 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
300 #ifdef SILC_DIST_INPLACE
301 /* Print schedule statistics to stdout */
303 void silc_schedule_stats(SilcSchedule schedule)
306 fprintf(stdout, "Schedule %p statistics:\n\n", schedule);
307 fprintf(stdout, "Num FD tasks : %lu (%lu bytes allocated)\n",
308 silc_hash_table_count(schedule->fd_queue),
309 sizeof(*ftask) * silc_hash_table_count(schedule->fd_queue));
310 fprintf(stdout, "Num Timeout tasks : %d (%d bytes allocated)\n",
311 silc_list_count(schedule->timeout_queue),
312 sizeof(struct SilcTaskTimeoutStruct) *
313 silc_list_count(schedule->timeout_queue));
314 fprintf(stdout, "Num Timeout freelist : %d (%d bytes allocated)\n",
315 silc_list_count(schedule->free_tasks),
316 sizeof(struct SilcTaskTimeoutStruct) *
317 silc_list_count(schedule->free_tasks));
319 #endif /* SILC_DIST_INPLACE */
321 /****************************** Public API **********************************/
323 /* Initializes the scheduler. This returns the scheduler context that
324 is given as arugment usually to all silc_schedule_* functions.
325 The `max_tasks' indicates the number of maximum tasks that the
326 scheduler can handle. The `app_context' is application specific
327 context that is delivered to task callbacks. */
329 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
331 SilcSchedule schedule;
333 SILC_LOG_DEBUG(("Initializing scheduler"));
335 schedule = silc_calloc(1, sizeof(*schedule));
340 silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
341 silc_schedule_fd_destructor, NULL, TRUE);
342 if (!schedule->fd_queue)
345 silc_list_init(schedule->timeout_queue, struct SilcTaskTimeoutStruct, next);
346 silc_list_init(schedule->free_tasks, struct SilcTaskTimeoutStruct, next);
348 schedule->app_context = app_context;
349 schedule->valid = TRUE;
350 schedule->max_tasks = max_tasks;
352 /* Allocate scheduler lock */
353 silc_mutex_alloc(&schedule->lock);
355 /* Initialize the platform specific scheduler. */
356 schedule->internal = schedule_ops.init(schedule, app_context);
358 /* Timeout freelist garbage collection */
359 silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
365 /* Uninitializes the schedule. This is called when the program is ready
366 to end. This removes all tasks and task queues. Returns FALSE if the
367 scheduler could not be uninitialized. This happens when the scheduler
368 is still valid and silc_schedule_stop has not been called. */
370 SilcBool silc_schedule_uninit(SilcSchedule schedule)
374 SILC_LOG_DEBUG(("Uninitializing scheduler"));
376 if (schedule->valid == TRUE)
379 /* Dispatch all timeouts before going away */
380 SILC_SCHEDULE_LOCK(schedule);
381 silc_schedule_dispatch_timeout(schedule, TRUE);
382 SILC_SCHEDULE_UNLOCK(schedule);
384 /* Deliver signals before going away */
385 if (schedule->signal_tasks) {
386 schedule_ops.signals_call(schedule, schedule->internal);
387 schedule->signal_tasks = FALSE;
390 /* Unregister all tasks */
391 silc_schedule_task_del(schedule, SILC_ALL_TASKS);
392 silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
394 /* Delete timeout task freelist */
395 silc_list_start(schedule->free_tasks);
396 while ((task = silc_list_get(schedule->free_tasks)))
399 /* Unregister all task queues */
400 silc_hash_table_free(schedule->fd_queue);
402 /* Uninit the platform specific scheduler. */
403 schedule_ops.uninit(schedule, schedule->internal);
405 silc_mutex_free(schedule->lock);
411 /* Stops the schedule even if it is not supposed to be stopped yet.
412 After calling this, one should call silc_schedule_uninit (after the
413 silc_schedule has returned). */
415 void silc_schedule_stop(SilcSchedule schedule)
417 SILC_LOG_DEBUG(("Stopping scheduler"));
418 SILC_SCHEDULE_LOCK(schedule);
419 schedule->valid = FALSE;
420 SILC_SCHEDULE_UNLOCK(schedule);
423 /* Runs the scheduler once and then returns. */
425 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
427 struct timeval timeout;
430 if (!schedule->is_locked)
431 SILC_SCHEDULE_LOCK(schedule);
434 SILC_LOG_DEBUG(("In scheduler loop"));
436 /* Deliver signals if any has been set to be called */
437 if (schedule->signal_tasks) {
438 SILC_SCHEDULE_UNLOCK(schedule);
439 schedule_ops.signals_call(schedule, schedule->internal);
440 schedule->signal_tasks = FALSE;
441 SILC_SCHEDULE_LOCK(schedule);
444 /* Check if scheduler is valid */
445 if (schedule->valid == FALSE) {
446 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
447 if (!schedule->is_locked)
448 SILC_SCHEDULE_UNLOCK(schedule);
452 /* Calculate next timeout for silc_select(). This is the timeout value
453 when at earliest some of the timeout tasks expire. This may dispatch
454 already expired timeouts. */
455 silc_schedule_select_timeout(schedule);
457 /* Check if scheduler is valid */
458 if (schedule->valid == FALSE) {
459 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
460 if (!schedule->is_locked)
461 SILC_SCHEDULE_UNLOCK(schedule);
465 if (timeout_usecs >= 0) {
467 timeout.tv_usec = timeout_usecs;
468 schedule->timeout = timeout;
469 schedule->has_timeout = TRUE;
472 /* This is the main silc_select(). The program blocks here until some
473 of the selected file descriptors change status or the selected
475 SILC_LOG_DEBUG(("Select"));
476 ret = schedule_ops.select(schedule, schedule->internal);
481 SILC_LOG_DEBUG(("Running timeout tasks"));
482 if (silc_list_count(schedule->timeout_queue))
483 silc_schedule_dispatch_timeout(schedule, FALSE);
489 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
492 /* There is some data available now */
493 SILC_LOG_DEBUG(("Running fd tasks"));
494 silc_schedule_dispatch_fd(schedule);
498 if (timeout_usecs >= 0)
502 if (!schedule->is_locked)
503 SILC_SCHEDULE_UNLOCK(schedule);
508 /* The SILC scheduler. This is actually the main routine in SILC programs.
509 When this returns the program is to be ended. Before this function can
510 be called, one must call silc_schedule_init function. */
512 void silc_schedule(SilcSchedule schedule)
514 SILC_LOG_DEBUG(("Running scheduler"));
516 if (schedule->valid == FALSE) {
517 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
521 /* Start the scheduler loop */
522 SILC_SCHEDULE_LOCK(schedule);
523 schedule->is_locked = TRUE;
524 silc_schedule_one(schedule, -1);
525 SILC_SCHEDULE_UNLOCK(schedule);
528 /* Wakes up the scheduler. This is used only in multi-threaded
529 environments where threads may add new tasks or remove old tasks
530 from task queues. This is called to wake up the scheduler in the
531 main thread so that it detects the changes in the task queues.
532 If threads support is not compiled in this function has no effect.
533 Implementation of this function is platform specific. */
535 void silc_schedule_wakeup(SilcSchedule schedule)
538 SILC_LOG_DEBUG(("Wakeup scheduler"));
539 SILC_SCHEDULE_LOCK(schedule);
540 schedule_ops.wakeup(schedule, schedule->internal);
541 SILC_SCHEDULE_UNLOCK(schedule);
545 /* Returns the application specific context that was saved into the
546 scheduler in silc_schedule_init function. The context is also
547 returned to application in task callback functions, but this function
548 may be used to get it as well if needed. */
550 void *silc_schedule_get_context(SilcSchedule schedule)
552 return schedule->app_context;
555 /* Add new task to the scheduler */
557 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
558 SilcTaskCallback callback, void *context,
559 long seconds, long useconds,
562 SilcTask task = NULL;
564 if (!schedule->valid)
567 SILC_SCHEDULE_LOCK(schedule);
569 if (type == SILC_TASK_TIMEOUT) {
570 SilcTaskTimeout tmp, prev, ttask;
572 ttask = silc_list_get(schedule->free_tasks);
574 ttask = silc_calloc(1, sizeof(*ttask));
578 silc_list_del(schedule->free_tasks, ttask);
580 ttask->header.type = 1;
581 ttask->header.callback = callback;
582 ttask->header.context = context;
583 ttask->header.valid = TRUE;
586 silc_gettimeofday(&ttask->timeout);
587 if ((seconds + useconds) > 0) {
588 ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
589 ttask->timeout.tv_usec += (useconds % 1000000L);
590 if (ttask->timeout.tv_usec >= 1000000L) {
591 ttask->timeout.tv_sec += 1;
592 ttask->timeout.tv_usec -= 1000000L;
596 SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
599 /* Add task to correct spot so that the first task in the list has
600 the earliest timeout. */
601 silc_list_start(schedule->timeout_queue);
603 while ((tmp = silc_list_get(schedule->timeout_queue)) != SILC_LIST_END) {
604 /* If we have shorter timeout, we have found our spot */
605 if (silc_compare_timeval(&ttask->timeout, &tmp->timeout)) {
606 silc_list_insert(schedule->timeout_queue, prev, ttask);
612 silc_list_add(schedule->timeout_queue, ttask);
614 task = (SilcTask)ttask;
616 } else if (type == SILC_TASK_FD) {
617 /* Check if fd is already added */
618 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
619 NULL, (void **)&task))
622 /* Check max tasks */
623 if (schedule->max_tasks > 0 &&
624 silc_hash_table_count(schedule->fd_queue) >= schedule->max_tasks) {
625 SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
629 SilcTaskFd ftask = silc_calloc(1, sizeof(*ftask));
633 SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
635 ftask->header.type = 0;
636 ftask->header.callback = callback;
637 ftask->header.context = context;
638 ftask->header.valid = TRUE;
639 ftask->events = SILC_TASK_READ;
643 silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask);
645 task = (SilcTask)ftask;
647 } else if (type == SILC_TASK_SIGNAL) {
648 SILC_SCHEDULE_UNLOCK(schedule);
649 schedule_ops.signal_register(schedule, schedule->internal, (int)fd,
656 SILC_SCHEDULE_UNLOCK(schedule);
660 /* Invalidates task */
662 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
664 if (task == SILC_ALL_TASKS) {
665 SilcHashTableList htl;
667 SILC_LOG_DEBUG(("Unregister all tasks"));
669 SILC_SCHEDULE_LOCK(schedule);
671 /* Delete from fd queue */
672 silc_hash_table_list(schedule->fd_queue, &htl);
673 while (silc_hash_table_get(&htl, NULL, (void **)&task))
675 silc_hash_table_list_reset(&htl);
677 /* Delete from timeout queue */
678 silc_list_start(schedule->timeout_queue);
679 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
683 SILC_SCHEDULE_UNLOCK(schedule);
687 SILC_LOG_DEBUG(("Unregistering task %p", task));
688 SILC_SCHEDULE_LOCK(schedule);
690 SILC_SCHEDULE_UNLOCK(schedule);
693 /* Invalidate task by fd */
695 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
697 SilcTask task = NULL;
699 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
701 SILC_SCHEDULE_LOCK(schedule);
703 /* fd is unique, so there is only one task with this fd in the table */
704 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd), NULL,
708 SILC_SCHEDULE_UNLOCK(schedule);
710 /* If it is signal, remove it */
712 schedule_ops.signal_unregister(schedule, schedule->internal, fd);
715 /* Invalidate task by task callback. */
717 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
718 SilcTaskCallback callback)
721 SilcHashTableList htl;
723 SILC_LOG_DEBUG(("Unregister task by callback"));
725 SILC_SCHEDULE_LOCK(schedule);
727 /* Delete from fd queue */
728 silc_hash_table_list(schedule->fd_queue, &htl);
729 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
730 if (task->callback == callback)
733 silc_hash_table_list_reset(&htl);
735 /* Delete from timeout queue */
736 silc_list_start(schedule->timeout_queue);
737 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
739 if (task->callback == callback)
743 SILC_SCHEDULE_UNLOCK(schedule);
746 /* Invalidate task by context. */
748 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
751 SilcHashTableList htl;
753 SILC_LOG_DEBUG(("Unregister task by context"));
755 SILC_SCHEDULE_LOCK(schedule);
757 /* Delete from fd queue */
758 silc_hash_table_list(schedule->fd_queue, &htl);
759 while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
760 if (task->context == context)
763 silc_hash_table_list_reset(&htl);
765 /* Delete from timeout queue */
766 silc_list_start(schedule->timeout_queue);
767 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
769 if (task->context == context)
773 SILC_SCHEDULE_UNLOCK(schedule);
776 /* Invalidate task by all */
778 void silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
779 SilcTaskCallback callback, void *context)
783 SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
785 /* For fd task, callback and context is irrelevant as fd is unique */
787 silc_schedule_task_del_by_fd(schedule, fd);
789 SILC_SCHEDULE_LOCK(schedule);
791 /* Delete from timeout queue */
792 silc_list_start(schedule->timeout_queue);
793 while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
795 if (task->callback == callback && task->context == context)
799 SILC_SCHEDULE_UNLOCK(schedule);
802 /* Sets a file descriptor to be listened by scheduler. One can call this
803 directly if wanted. This can be called multiple times for one file
804 descriptor to set different iomasks. */
806 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
807 SilcTaskEvent mask, SilcBool send_events)
811 if (!schedule->valid)
814 SILC_SCHEDULE_LOCK(schedule);
816 if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
817 NULL, (void **)&task)) {
820 task->revents = mask;
821 silc_schedule_dispatch_fd(schedule);
825 SILC_SCHEDULE_UNLOCK(schedule);
828 /* Removes a file descriptor from listen list. */
830 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
832 silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);