5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 1998 - 2001 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; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
22 #include "silcincludes.h"
24 /* Routine to remove the task. Implemented in silctask.c. */
25 int silc_task_remove(SilcTaskQueue queue, SilcTask task);
27 /* Routine to compare task timeouts. Implemented in silctask.c. */
28 int silc_task_timeout_compare(struct timeval *smaller,
29 struct timeval *bigger);
31 /* System specific routines. Implemented under unix/ and win32/. */
33 /* System specific select(). */
34 int silc_select(int n, fd_set *readfds, fd_set *writefds,
35 fd_set *exceptfds, struct timeval *timeout);
37 /* Initializes the wakeup of the scheduler. In multi-threaded environment
38 the scheduler needs to be wakenup when tasks are added or removed from
39 the task queues. This will initialize the wakeup for the scheduler.
40 Any tasks that needs to be registered must be registered to the `queue'.
41 It is guaranteed that the scheduler will automatically free any
42 registered tasks in this queue. This is system specific routine. */
43 void *silc_schedule_wakeup_init(void *queue);
45 /* Uninitializes the system specific wakeup. */
46 void silc_schedule_wakeup_uninit(void *context);
48 /* Wakes up the scheduler. This is platform specific routine */
49 void silc_schedule_wakeup_internal(void *context);
51 /* Structure holding list of file descriptors, scheduler is supposed to
52 be listenning. The max_fd field is the maximum number of possible file
53 descriptors in the list. This value is set at the initialization
54 of the scheduler and it usually is the maximum number of connections
63 SILC Scheduler structure.
65 This is the actual schedule object in SILC. Both SILC client and server
66 uses this same scheduler. Actually, this scheduler could be used by any
67 program needing scheduling.
69 Following short description of the fields:
71 SilcTaskQueue fd_queue
73 Task queue hook for non-timeout tasks. Usually this means that these
74 tasks perform different kind of I/O on file descriptors. File
75 descriptors are usually network sockets but they actually can be
76 any file descriptors. This hook is initialized in silc_schedule_init
77 function. Timeout tasks should not be added to this queue because
78 they will never expire.
80 SilcTaskQueue timeout_queue
82 Task queue hook for timeout tasks. This hook is reserved specificly
83 for tasks with timeout. Non-timeout tasks should not be added to this
84 queue because they will never get scheduled. This hook is also
85 initialized in silc_schedule_init function.
87 SilcTaskQueue generic_queue
89 Task queue hook for generic tasks. This hook is reserved specificly
90 for generic tasks, tasks that apply to all file descriptors, except
91 to those that have specificly registered a non-timeout task. This hook
92 is also initialized in silc_schedule_init function.
94 SilcScheduleFdList fd_list
96 List of file descriptors the scheduler is supposed to be listenning.
97 This is updated internally.
99 struct timeval *timeout;
101 Pointer to the schedules next timeout. Value of this timeout is
102 automatically updated in the silc_schedule function.
106 Marks validity of the scheduler. This is a boolean value. When this
107 is false the scheduler is terminated and the program will end. This
108 set to true when the scheduler is initialized with silc_schedule_init
114 File descriptor sets for select(). These are automatically managed
115 by the scheduler and should not be touched otherwise.
119 Number of maximum file descriptors for select(). This, as well, is
120 managed automatically by the scheduler and should be considered to
121 be read-only field otherwise.
125 System specific wakeup context. On multi-threaded environments the
126 scheduler needs to be wakenup (in the thread) when tasks are added
127 or removed. This is initialized by silc_schedule_wakeup_init.
130 struct SilcScheduleStruct {
131 SilcTaskQueue fd_queue;
132 SilcTaskQueue timeout_queue;
133 SilcTaskQueue generic_queue;
134 SilcScheduleFdList fd_list;
135 struct timeval *timeout;
141 SILC_MUTEX_DEFINE(lock);
145 /* Initializes the scheduler. Sets the non-timeout task queue hook and
146 the timeout task queue hook. This must be called before the scheduler
147 is able to work. This will allocate the queue pointers if they are
148 not allocated. Returns the scheduler context that must be freed by
149 the silc_schedule_uninit function. */
151 SilcSchedule silc_schedule_init(SilcTaskQueue *fd_queue,
152 SilcTaskQueue *timeout_queue,
153 SilcTaskQueue *generic_queue,
156 SilcSchedule schedule;
159 SILC_LOG_DEBUG(("Initializing scheduler"));
161 schedule = silc_calloc(1, sizeof(*schedule));
163 /* Register the task queues if they are not registered already. In SILC
164 we have by default three task queues. One task queue for non-timeout
165 tasks which perform different kind of I/O on file descriptors, timeout
166 task queue for timeout tasks, and, generic non-timeout task queue whose
167 tasks apply to all connections. */
169 silc_task_queue_alloc(schedule, fd_queue, TRUE);
171 silc_task_queue_alloc(schedule, timeout_queue, TRUE);
173 silc_task_queue_alloc(schedule, generic_queue, TRUE);
175 /* Initialize the scheduler */
176 schedule->fd_queue = *fd_queue;
177 schedule->timeout_queue = *timeout_queue;
178 schedule->generic_queue = *generic_queue;
179 schedule->fd_list.fd = silc_calloc(max_fd, sizeof(*schedule->fd_list.fd));
180 schedule->fd_list.last_fd = 0;
181 schedule->fd_list.max_fd = max_fd;
182 schedule->timeout = NULL;
183 schedule->valid = TRUE;
184 FD_ZERO(&schedule->in);
185 FD_ZERO(&schedule->out);
186 schedule->max_fd = -1;
187 for (i = 0; i < max_fd; i++)
188 schedule->fd_list.fd[i] = -1;
190 silc_mutex_alloc(&schedule->lock);
192 /* Initialize the wakeup */
193 schedule->wakeup = silc_schedule_wakeup_init(schedule->fd_queue);
198 /* Uninitializes the schedule. This is called when the program is ready
199 to end. This removes all tasks and task queues. Returns FALSE if the
200 scheduler could not be uninitialized. This happens when the scheduler
201 is still valid and silc_schedule_stop has not been called. */
203 bool silc_schedule_uninit(SilcSchedule schedule)
206 SILC_LOG_DEBUG(("Uninitializing scheduler"));
208 if (schedule->valid == TRUE)
211 /* Unregister all tasks */
212 if (schedule->fd_queue)
213 silc_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
214 if (schedule->timeout_queue)
215 silc_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
216 if (schedule->generic_queue)
217 silc_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
219 /* Unregister all task queues */
220 if (schedule->fd_queue)
221 silc_task_queue_free(schedule->fd_queue);
222 if (schedule->timeout_queue)
223 silc_task_queue_free(schedule->timeout_queue);
224 if (schedule->generic_queue)
225 silc_task_queue_free(schedule->generic_queue);
227 /* Clear the fd list */
228 if (schedule->fd_list.fd) {
229 memset(schedule->fd_list.fd, -1, schedule->fd_list.max_fd);
230 silc_free(schedule->fd_list.fd);
233 /* Uninit the wakeup */
234 silc_schedule_wakeup_uninit(schedule->wakeup);
236 silc_mutex_free(schedule->lock);
241 /* Stops the schedule even if it is not supposed to be stopped yet.
242 After calling this, one should call silc_schedule_uninit (after the
243 silc_schedule has returned). */
245 void silc_schedule_stop(SilcSchedule schedule)
247 SILC_LOG_DEBUG(("Stopping scheduler"));
248 silc_mutex_lock(schedule->lock);
249 schedule->valid = FALSE;
250 silc_mutex_unlock(schedule->lock);
253 /* Sets a file descriptor to be listened by select() in scheduler. One can
254 call this directly if wanted. This can be called multiple times for
255 one file descriptor to set different iomasks. */
257 void silc_schedule_set_listen_fd(SilcSchedule schedule, int fd, uint32 iomask)
259 silc_mutex_lock(schedule->lock);
261 schedule->fd_list.fd[fd] = iomask;
263 if (fd > schedule->fd_list.last_fd)
264 schedule->fd_list.last_fd = fd;
266 silc_mutex_unlock(schedule->lock);
269 /* Removes a file descriptor from listen list. */
271 void silc_schedule_unset_listen_fd(SilcSchedule schedule, int fd)
273 silc_mutex_lock(schedule->lock);
275 schedule->fd_list.fd[fd] = -1;
277 if (fd == schedule->fd_list.last_fd) {
280 for (i = fd; i >= 0; i--)
281 if (schedule->fd_list.fd[i] != -1)
284 schedule->fd_list.last_fd = i < 0 ? 0 : i;
287 silc_mutex_unlock(schedule->lock);
290 /* Executes tasks matching the file descriptor set by select(). The task
291 remains on the task queue after execution. Invalid tasks are removed
292 here from the task queue. This macro is used by silc_schedule function.
293 We don't have to care about the tasks priority here because the tasks
294 are sorted in their priority order already at the registration phase. */
295 /* This must be called holding the schedule->lock and the
296 schedule->fd_queue->lock. */
298 #define SILC_SCHEDULE_RUN_TASKS \
300 queue = schedule->fd_queue; \
301 if (queue && queue->valid == TRUE && queue->task) { \
302 task = queue->task; \
304 /* Walk thorugh all tasks in the particular task queue and \
305 execute the callback functions of those tasks matching the \
306 fd set by select(). */ \
308 /* Validity of the task is checked always before and after \
309 execution beacuse the task might have been unregistered \
310 in the callback function, ie. it is not valid anymore. */ \
313 /* Task ready for reading */ \
314 if ((FD_ISSET(task->fd, &schedule->in)) && \
315 (task->iomask & (1L << SILC_TASK_READ))) { \
316 silc_mutex_unlock(queue->lock); \
317 silc_mutex_unlock(schedule->lock); \
318 task->callback(queue, SILC_TASK_READ, task->context, task->fd); \
319 silc_mutex_lock(schedule->lock); \
320 silc_mutex_lock(queue->lock); \
326 /* Task ready for writing */ \
327 if ((FD_ISSET(task->fd, &schedule->out)) && \
328 (task->iomask & (1L << SILC_TASK_WRITE))) { \
329 silc_mutex_unlock(queue->lock); \
330 silc_mutex_unlock(schedule->lock); \
331 task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
332 silc_mutex_lock(schedule->lock); \
333 silc_mutex_lock(queue->lock); \
338 if (!task->valid) { \
339 /* Invalid (unregistered) tasks are removed from the \
341 if (queue->task == task->next) { \
342 silc_task_remove(queue, task); \
347 silc_task_remove(queue, task->prev); \
351 /* Break if there isn't more tasks in the queue */ \
352 if (queue->task == task->next) \
360 /* Selects tasks to be listened by select(). These are the non-timeout
361 tasks. This checks the scheduler's fd list. This macro is used by
362 silc_schedule function. */
363 /* This must be called holding schedule->lock. */
365 #define SILC_SCHEDULE_SELECT_TASKS \
367 for (i = 0; i <= schedule->fd_list.last_fd; i++) { \
368 if (schedule->fd_list.fd[i] != -1) { \
370 /* Set the max fd value for select() to listen */ \
371 if (i > schedule->max_fd) \
372 schedule->max_fd = i; \
374 /* Add tasks for reading */ \
375 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) \
376 FD_SET(i, &schedule->in); \
378 /* Add tasks for writing */ \
379 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
380 FD_SET(i, &schedule->out); \
385 /* Executes all tasks whose timeout has expired. The task is removed from
386 the task queue after the callback function has returned. Also, invalid
387 tasks are removed here. The current time must be get before calling this
388 macro. This macro is used by silc_schedule function. We don't have to
389 care about priorities because tasks are already sorted in their priority
390 order at the registration phase. */
391 /* This must be called with holding the schedule->lock and the
392 schedule->timeout_queue->lock */
394 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS \
396 queue = schedule->timeout_queue; \
397 if (queue && queue->valid == TRUE && queue->task) { \
398 task = queue->task; \
400 /* Walk thorugh all tasks in the particular task queue \
401 and run all the expired tasks. */ \
403 /* Execute the task if the timeout has expired */ \
404 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
406 /* Task ready for reading */ \
408 if ((task->iomask & (1L << SILC_TASK_READ))) { \
409 silc_mutex_unlock(queue->lock); \
410 silc_mutex_unlock(schedule->lock); \
411 task->callback(queue, SILC_TASK_READ, \
412 task->context, task->fd); \
413 silc_mutex_lock(schedule->lock); \
414 silc_mutex_lock(queue->lock); \
418 /* Task ready for writing */ \
420 if ((task->iomask & (1L << SILC_TASK_WRITE))) { \
421 silc_mutex_unlock(queue->lock); \
422 silc_mutex_unlock(schedule->lock); \
423 task->callback(queue, SILC_TASK_WRITE, \
424 task->context, task->fd); \
425 silc_mutex_lock(schedule->lock); \
426 silc_mutex_lock(queue->lock); \
430 /* Break if there isn't more tasks in the queue */ \
431 if (queue->task == task->next) { \
432 /* Remove the task from queue */ \
433 silc_task_remove(queue, task); \
439 /* Remove the task from queue */ \
440 silc_task_remove(queue, task->prev); \
442 /* The timeout hasn't expired, check for next one */ \
444 /* Break if there isn't more tasks in the queue */ \
445 if (queue->task == task->next) \
454 /* Calculates next timeout for select(). This is the timeout value
455 when at earliest some of the timeout tasks expire. If this is in the
456 past, they will be run now. This macro is used by the silc_schedule
458 /* This must be called with holding the schedule->lock and the
459 schedule->timeout_queue->lock */
461 #define SILC_SCHEDULE_SELECT_TIMEOUT \
463 if (schedule->timeout_queue && schedule->timeout_queue->valid == TRUE) { \
464 queue = schedule->timeout_queue; \
467 /* Get the current time */ \
468 silc_gettimeofday(&curtime); \
469 schedule->timeout = NULL; \
471 /* First task in the task queue has always the smallest timeout. */ \
472 task = queue->task; \
474 if (task && task->valid == TRUE) { \
476 /* If the timeout is in past, we will run the task and all other \
477 timeout tasks from the past. */ \
478 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
479 SILC_SCHEDULE_RUN_TIMEOUT_TASKS; \
481 /* The task(s) has expired and doesn't exist on the task queue \
482 anymore. We continue with new timeout. */ \
483 queue = schedule->timeout_queue; \
484 task = queue->task; \
485 if (task == NULL || task->valid == FALSE) \
490 /* Calculate the next timeout for select() */ \
491 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec; \
492 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
493 if (queue->timeout.tv_sec < 0) \
494 queue->timeout.tv_sec = 0; \
496 /* We wouldn't want to go under zero, check for it. */ \
497 if (queue->timeout.tv_usec < 0) { \
498 queue->timeout.tv_sec -= 1; \
499 if (queue->timeout.tv_sec < 0) \
500 queue->timeout.tv_sec = 0; \
501 queue->timeout.tv_usec += 1000000L; \
504 /* We've got the timeout value */ \
507 /* Task is not valid, remove it and try next one. */ \
508 silc_task_remove(queue, task); \
509 task = queue->task; \
510 if (queue->task == NULL) \
514 /* Save the timeout */ \
516 schedule->timeout = &queue->timeout; \
520 /* Execute generic tasks. These are executed only and only if for the
521 specific fd there wasn't other non-timeout tasks. This checks the earlier
522 set fd list, thus the generic tasks apply to all specified fd's. All the
523 generic tasks are executed at once. */
524 /* This must be called holding the schedule->lock and the
525 schedule->generic_queue->lock. */
527 #define SILC_SCHEDULE_RUN_GENERIC_TASKS \
529 if (is_run == FALSE) { \
530 SILC_LOG_DEBUG(("Running generic tasks")); \
531 for (i = 0; i <= schedule->fd_list.last_fd; i++) \
532 if (schedule->fd_list.fd[i] != -1) { \
534 /* Check whether this fd is select()ed. */ \
535 if ((FD_ISSET(i, &schedule->in)) || (FD_ISSET(i, &schedule->out))) { \
537 /* It was selected. Now find the tasks from task queue and execute \
538 all generic tasks. */ \
539 if (schedule->generic_queue && schedule->generic_queue->valid) { \
540 queue = schedule->generic_queue; \
545 task = queue->task; \
548 /* Validity of the task is checked always before and after \
549 execution beacuse the task might have been unregistered \
550 in the callback function, ie. it is not valid anymore. */ \
552 if (task->valid && schedule->fd_list.fd[i] != -1) { \
553 /* Task ready for reading */ \
554 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) { \
555 silc_mutex_unlock(queue->lock); \
556 silc_mutex_unlock(schedule->lock); \
557 task->callback(queue, SILC_TASK_READ, \
559 silc_mutex_lock(schedule->lock); \
560 silc_mutex_lock(queue->lock); \
564 if (task->valid && schedule->fd_list.fd[i] != -1) { \
565 /* Task ready for writing */ \
566 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) { \
567 silc_mutex_unlock(queue->lock); \
568 silc_mutex_unlock(schedule->lock); \
569 task->callback(queue, SILC_TASK_WRITE, \
571 silc_mutex_lock(schedule->lock); \
572 silc_mutex_lock(queue->lock); \
576 if (!task->valid) { \
577 /* Invalid (unregistered) tasks are removed from the \
579 if (queue->task == task->next) { \
580 silc_task_remove(queue, task); \
585 silc_task_remove(queue, task->prev); \
589 /* Break if there isn't more tasks in the queue */ \
590 if (queue->task == task->next) \
601 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
603 struct timeval timeout;
607 struct timeval curtime;
610 SILC_LOG_DEBUG(("In scheduler loop"));
612 if (!schedule->is_locked)
613 silc_mutex_lock(schedule->lock);
615 /* If the task queues aren't initialized or we aren't valid anymore
617 if ((!schedule->fd_queue && !schedule->timeout_queue
618 && !schedule->generic_queue) || schedule->valid == FALSE) {
619 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
620 if (!schedule->is_locked)
621 silc_mutex_unlock(schedule->lock);
625 /* Clear everything */
626 FD_ZERO(&schedule->in);
627 FD_ZERO(&schedule->out);
628 schedule->max_fd = -1;
631 /* Calculate next timeout for silc_select(). This is the timeout value
632 when at earliest some of the timeout tasks expire. */
633 silc_mutex_lock(schedule->timeout_queue->lock);
634 SILC_SCHEDULE_SELECT_TIMEOUT;
635 silc_mutex_unlock(schedule->timeout_queue->lock);
637 /* Add the file descriptors to the fd sets. These are the non-timeout
638 tasks. The silc_select() listens to these file descriptors. */
639 SILC_SCHEDULE_SELECT_TASKS;
641 if (schedule->max_fd == -1 && !schedule->timeout) {
642 if (!schedule->is_locked)
643 silc_mutex_unlock(schedule->lock);
647 if (schedule->timeout) {
648 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
649 schedule->timeout->tv_usec));
652 if (timeout_usecs >= 0) {
654 timeout.tv_usec = timeout_usecs;
655 schedule->timeout = &timeout;
658 silc_mutex_unlock(schedule->lock);
660 /* This is the main select(). The program blocks here until some
661 of the selected file descriptors change status or the selected
663 SILC_LOG_DEBUG(("Select"));
664 ret = silc_select(schedule->max_fd + 1, &schedule->in,
665 &schedule->out, 0, schedule->timeout);
667 silc_mutex_lock(schedule->lock);
674 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
678 SILC_LOG_DEBUG(("Running timeout tasks"));
679 silc_mutex_lock(schedule->timeout_queue->lock);
680 silc_gettimeofday(&curtime);
681 SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
682 silc_mutex_unlock(schedule->timeout_queue->lock);
685 /* There is some data available now */
686 SILC_LOG_DEBUG(("Running non-timeout tasks"));
687 silc_mutex_lock(schedule->fd_queue->lock);
688 SILC_SCHEDULE_RUN_TASKS;
689 silc_mutex_unlock(schedule->fd_queue->lock);
691 silc_mutex_lock(schedule->generic_queue->lock);
692 SILC_SCHEDULE_RUN_GENERIC_TASKS;
693 silc_mutex_unlock(schedule->generic_queue->lock);
697 if (!schedule->is_locked)
698 silc_mutex_unlock(schedule->lock);
703 /* The SILC scheduler. This is actually the main routine in SILC programs.
704 When this returns the program is to be ended. Before this function can
705 be called, one must call silc_schedule_init function. */
707 void silc_schedule(SilcSchedule schedule)
709 SILC_LOG_DEBUG(("Running scheduler"));
711 if (schedule->valid == FALSE) {
712 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
716 silc_mutex_lock(schedule->lock);
717 schedule->is_locked = TRUE;
719 /* Start the scheduler loop */
720 while (silc_schedule_one(schedule, -1))
723 silc_mutex_unlock(schedule->lock);
726 /* Wakes up the scheduler. This is used only in multi-threaded
727 environments where threads may add new tasks or remove old tasks
728 from task queues. This is called to wake up the scheduler in the
729 main thread so that it detects the changes in the task queues.
730 If threads support is not compiled in this function has no effect.
731 Implementation of this function is platform specific. */
733 void silc_schedule_wakeup(SilcSchedule schedule)
736 SILC_LOG_DEBUG(("Wakeup scheduler"));
737 silc_mutex_lock(schedule->lock);
738 silc_schedule_wakeup_internal(schedule->wakeup);
739 silc_mutex_unlock(schedule->lock);