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.
21 /* XXX on multi-threads the task queue locking is missing here. */
23 #include "silcincludes.h"
25 /* Routine to remove the task. Implemented in silctask.c. */
26 int silc_task_remove(SilcTaskQueue queue, SilcTask task);
28 /* System specific routines. Implemented under unix/ and win32/. */
30 /* System specific select(). */
31 int silc_select(int n, fd_set *readfds, fd_set *writefds,
32 fd_set *exceptfds, struct timeval *timeout);
34 /* Initializes the wakeup of the scheduler. In multi-threaded environment
35 the scheduler needs to be wakenup when tasks are added or removed from
36 the task queues. This will initialize the wakeup for the scheduler.
37 Any tasks that needs to be registered must be registered to the `queue'.
38 It is guaranteed that the scheduler will automatically free any
39 registered tasks in this queue. This is system specific routine. */
40 void *silc_schedule_wakeup_init(void *queue);
42 /* Uninitializes the system specific wakeup. */
43 void silc_schedule_wakeup_uninit(void *context);
45 /* Wakes up the scheduler. This is platform specific routine */
46 void silc_schedule_wakeup_internal(void *context);
48 /* Structure holding list of file descriptors, scheduler is supposed to
49 be listenning. The max_fd field is the maximum number of possible file
50 descriptors in the list. This value is set at the initialization
51 of the scheduler and it usually is the maximum number of connections
60 SILC Scheduler structure.
62 This is the actual schedule object in SILC. Both SILC client and server
63 uses this same scheduler. Actually, this scheduler could be used by any
64 program needing scheduling.
66 Following short description of the fields:
68 SilcTaskQueue fd_queue
70 Task queue hook for non-timeout tasks. Usually this means that these
71 tasks perform different kind of I/O on file descriptors. File
72 descriptors are usually network sockets but they actually can be
73 any file descriptors. This hook is initialized in silc_schedule_init
74 function. Timeout tasks should not be added to this queue because
75 they will never expire.
77 SilcTaskQueue timeout_queue
79 Task queue hook for timeout tasks. This hook is reserved specificly
80 for tasks with timeout. Non-timeout tasks should not be added to this
81 queue because they will never get scheduled. This hook is also
82 initialized in silc_schedule_init function.
84 SilcTaskQueue generic_queue
86 Task queue hook for generic tasks. This hook is reserved specificly
87 for generic tasks, tasks that apply to all file descriptors, except
88 to those that have specificly registered a non-timeout task. This hook
89 is also initialized in silc_schedule_init function.
91 SilcScheduleFdList fd_list
93 List of file descriptors the scheduler is supposed to be listenning.
94 This is updated internally.
96 struct timeval *timeout;
98 Pointer to the schedules next timeout. Value of this timeout is
99 automatically updated in the silc_schedule function.
103 Marks validity of the scheduler. This is a boolean value. When this
104 is false the scheduler is terminated and the program will end. This
105 set to true when the scheduler is initialized with silc_schedule_init
111 File descriptor sets for select(). These are automatically managed
112 by the scheduler and should not be touched otherwise.
116 Number of maximum file descriptors for select(). This, as well, is
117 managed automatically by the scheduler and should be considered to
118 be read-only field otherwise.
122 System specific wakeup context. On multi-threaded environments the
123 scheduler needs to be wakenup (in the thread) when tasks are added
124 or removed. This is initialized by silc_schedule_wakeup_init.
127 struct SilcScheduleStruct {
128 SilcTaskQueue fd_queue;
129 SilcTaskQueue timeout_queue;
130 SilcTaskQueue generic_queue;
131 SilcScheduleFdList fd_list;
132 struct timeval *timeout;
138 SILC_MUTEX_DEFINE(lock);
141 /* Initializes the scheduler. Sets the non-timeout task queue hook and
142 the timeout task queue hook. This must be called before the scheduler
143 is able to work. This will allocate the queue pointers if they are
144 not allocated. Returns the scheduler context that must be freed by
145 the silc_schedule_uninit function. */
147 SilcSchedule silc_schedule_init(SilcTaskQueue *fd_queue,
148 SilcTaskQueue *timeout_queue,
149 SilcTaskQueue *generic_queue,
152 SilcSchedule schedule;
155 SILC_LOG_DEBUG(("Initializing scheduler"));
157 schedule = silc_calloc(1, sizeof(*schedule));
159 /* Register the task queues if they are not registered already. In SILC
160 we have by default three task queues. One task queue for non-timeout
161 tasks which perform different kind of I/O on file descriptors, timeout
162 task queue for timeout tasks, and, generic non-timeout task queue whose
163 tasks apply to all connections. */
165 silc_task_queue_alloc(schedule, fd_queue, TRUE);
167 silc_task_queue_alloc(schedule, timeout_queue, TRUE);
169 silc_task_queue_alloc(schedule, generic_queue, TRUE);
171 /* Initialize the scheduler */
172 schedule->fd_queue = *fd_queue;
173 schedule->timeout_queue = *timeout_queue;
174 schedule->generic_queue = *generic_queue;
175 schedule->fd_list.fd = silc_calloc(max_fd, sizeof(*schedule->fd_list.fd));
176 schedule->fd_list.last_fd = 0;
177 schedule->fd_list.max_fd = max_fd;
178 schedule->timeout = NULL;
179 schedule->valid = TRUE;
180 FD_ZERO(&schedule->in);
181 FD_ZERO(&schedule->out);
182 schedule->max_fd = -1;
183 for (i = 0; i < max_fd; i++)
184 schedule->fd_list.fd[i] = -1;
186 silc_mutex_alloc(&schedule->lock);
188 /* Initialize the wakeup */
189 schedule->wakeup = silc_schedule_wakeup_init(schedule->fd_queue);
194 /* Uninitializes the schedule. This is called when the program is ready
195 to end. This removes all tasks and task queues. Returns FALSE if the
196 scheduler could not be uninitialized. This happens when the scheduler
197 is still valid and silc_schedule_stop has not been called. */
199 bool silc_schedule_uninit(SilcSchedule schedule)
202 SILC_LOG_DEBUG(("Uninitializing scheduler"));
204 if (schedule->valid == TRUE)
207 /* Unregister all tasks */
208 if (schedule->fd_queue)
209 silc_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
210 if (schedule->timeout_queue)
211 silc_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
212 if (schedule->generic_queue)
213 silc_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
215 /* Unregister all task queues */
216 if (schedule->fd_queue)
217 silc_task_queue_free(schedule->fd_queue);
218 if (schedule->timeout_queue)
219 silc_task_queue_free(schedule->timeout_queue);
220 if (schedule->generic_queue)
221 silc_task_queue_free(schedule->generic_queue);
223 /* Clear the fd list */
224 if (schedule->fd_list.fd) {
225 memset(schedule->fd_list.fd, -1, schedule->fd_list.max_fd);
226 silc_free(schedule->fd_list.fd);
229 /* Uninit the wakeup */
230 silc_schedule_wakeup_uninit(schedule->wakeup);
232 silc_mutex_free(schedule->lock);
237 /* Stops the schedule even if it is not supposed to be stopped yet.
238 After calling this, one should call silc_schedule_uninit (after the
239 silc_schedule has returned). */
241 void silc_schedule_stop(SilcSchedule schedule)
243 SILC_LOG_DEBUG(("Stopping scheduler"));
244 schedule->valid = FALSE;
247 /* Sets a file descriptor to be listened by select() in scheduler. One can
248 call this directly if wanted. This can be called multiple times for
249 one file descriptor to set different iomasks. */
251 void silc_schedule_set_listen_fd(SilcSchedule schedule, int fd, uint32 iomask)
253 silc_mutex_lock(schedule->lock);
255 schedule->fd_list.fd[fd] = iomask;
257 if (fd > schedule->fd_list.last_fd)
258 schedule->fd_list.last_fd = fd;
260 silc_mutex_unlock(schedule->lock);
263 /* Removes a file descriptor from listen list. */
265 void silc_schedule_unset_listen_fd(SilcSchedule schedule, int fd)
267 silc_mutex_lock(schedule->lock);
269 schedule->fd_list.fd[fd] = -1;
271 if (fd == schedule->fd_list.last_fd) {
274 for (i = fd; i >= 0; i--)
275 if (schedule->fd_list.fd[i] != -1)
278 schedule->fd_list.last_fd = i < 0 ? 0 : i;
281 silc_mutex_unlock(schedule->lock);
284 /* Executes tasks matching the file descriptor set by select(). The task
285 remains on the task queue after execution. Invalid tasks are removed
286 here from the task queue. This macro is used by silc_schedule function.
287 We don't have to care about the tasks priority here because the tasks
288 are sorted in their priority order already at the registration phase. */
290 #define SILC_SCHEDULE_RUN_TASKS \
292 queue = schedule->fd_queue; \
293 if (queue && queue->valid == TRUE && queue->task) { \
294 task = queue->task; \
296 /* Walk thorugh all tasks in the particular task queue and \
297 execute the callback functions of those tasks matching the \
298 fd set by select(). */ \
300 /* Validity of the task is checked always before and after \
301 execution beacuse the task might have been unregistered \
302 in the callback function, ie. it is not valid anymore. */ \
305 /* Task ready for reading */ \
306 if ((FD_ISSET(task->fd, &schedule->in)) && \
307 (task->iomask & (1L << SILC_TASK_READ))) { \
308 task->callback(queue, SILC_TASK_READ, task->context, task->fd); \
314 /* Task ready for writing */ \
315 if ((FD_ISSET(task->fd, &schedule->out)) && \
316 (task->iomask & (1L << SILC_TASK_WRITE))) { \
317 task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
322 if (!task->valid) { \
323 /* Invalid (unregistered) tasks are removed from the \
325 if (queue->task == task->next) { \
326 silc_task_remove(queue, task); \
331 silc_task_remove(queue, task->prev); \
335 /* Break if there isn't more tasks in the queue */ \
336 if (queue->task == task->next) \
344 /* Selects tasks to be listened by select(). These are the non-timeout
345 tasks. This checks the scheduler's fd list. This macro is used by
346 silc_schedule function. */
348 #define SILC_SCHEDULE_SELECT_TASKS \
350 for (i = 0; i <= schedule->fd_list.last_fd; i++) { \
351 if (schedule->fd_list.fd[i] != -1) { \
353 /* Set the max fd value for select() to listen */ \
354 if (i > schedule->max_fd) \
355 schedule->max_fd = i; \
357 /* Add tasks for reading */ \
358 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) \
359 FD_SET(i, &schedule->in); \
361 /* Add tasks for writing */ \
362 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
363 FD_SET(i, &schedule->out); \
368 /* Executes all tasks whose timeout has expired. The task is removed from
369 the task queue after the callback function has returned. Also, invalid
370 tasks are removed here. The current time must be get before calling this
371 macro. This macro is used by silc_schedule function. We don't have to
372 care about priorities because tasks are already sorted in their priority
373 order at the registration phase. */
375 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS \
377 queue = schedule->timeout_queue; \
378 if (queue && queue->valid == TRUE && queue->task) { \
379 task = queue->task; \
381 /* Walk thorugh all tasks in the particular task queue \
382 and run all the expired tasks. */ \
384 /* Execute the task if the timeout has expired */ \
385 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
387 /* Task ready for reading */ \
389 if ((task->iomask & (1L << SILC_TASK_READ))) \
390 task->callback(queue, SILC_TASK_READ, \
391 task->context, task->fd); \
394 /* Task ready for writing */ \
396 if ((task->iomask & (1L << SILC_TASK_WRITE))) \
397 task->callback(queue, SILC_TASK_WRITE, \
398 task->context, task->fd); \
401 /* Break if there isn't more tasks in the queue */ \
402 if (queue->task == task->next) { \
403 /* Remove the task from queue */ \
404 silc_task_remove(queue, task); \
410 /* Remove the task from queue */ \
411 silc_task_remove(queue, task->prev); \
413 /* The timeout hasn't expired, check for next one */ \
415 /* Break if there isn't more tasks in the queue */ \
416 if (queue->task == task->next) \
425 /* Calculates next timeout for select(). This is the timeout value
426 when at earliest some of the timeout tasks expire. If this is in the
427 past, they will be run now. This macro is used by the silc_schedule
430 #define SILC_SCHEDULE_SELECT_TIMEOUT \
432 if (schedule->timeout_queue && schedule->timeout_queue->valid == TRUE) { \
433 queue = schedule->timeout_queue; \
436 /* Get the current time */ \
437 silc_gettimeofday(&curtime); \
438 schedule->timeout = NULL; \
440 /* First task in the task queue has always the smallest timeout. */ \
441 task = queue->task; \
443 if (task && task->valid == TRUE) { \
445 /* If the timeout is in past, we will run the task and all other \
446 timeout tasks from the past. */ \
447 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
448 SILC_SCHEDULE_RUN_TIMEOUT_TASKS; \
450 /* The task(s) has expired and doesn't exist on the task queue \
451 anymore. We continue with new timeout. */ \
452 queue = schedule->timeout_queue; \
453 task = queue->task; \
454 if (task == NULL || task->valid == FALSE) \
459 /* Calculate the next timeout for select() */ \
460 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec; \
461 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
462 if (queue->timeout.tv_sec < 0) \
463 queue->timeout.tv_sec = 0; \
465 /* We wouldn't want to go under zero, check for it. */ \
466 if (queue->timeout.tv_usec < 0) { \
467 queue->timeout.tv_sec -= 1; \
468 if (queue->timeout.tv_sec < 0) \
469 queue->timeout.tv_sec = 0; \
470 queue->timeout.tv_usec += 1000000L; \
473 /* We've got the timeout value */ \
476 /* Task is not valid, remove it and try next one. */ \
477 silc_task_remove(queue, task); \
478 task = queue->task; \
479 if (queue->task == NULL) \
483 /* Save the timeout */ \
485 schedule->timeout = &queue->timeout; \
489 /* Execute generic tasks. These are executed only and only if for the
490 specific fd there wasn't other non-timeout tasks. This checks the earlier
491 set fd list, thus the generic tasks apply to all specified fd's. All the
492 generic tasks are executed at once. */
494 #define SILC_SCHEDULE_RUN_GENERIC_TASKS \
496 if (is_run == FALSE) { \
497 SILC_LOG_DEBUG(("Running generic tasks")); \
498 silc_mutex_lock(schedule->lock); \
499 for (i = 0; i <= schedule->fd_list.last_fd; i++) \
500 if (schedule->fd_list.fd[i] != -1) { \
502 /* Check whether this fd is select()ed. */ \
503 if ((FD_ISSET(i, &schedule->in)) || (FD_ISSET(i, &schedule->out))) { \
505 /* It was selected. Now find the tasks from task queue and execute \
506 all generic tasks. */ \
507 if (schedule->generic_queue && schedule->generic_queue->valid) { \
508 queue = schedule->generic_queue; \
513 task = queue->task; \
516 /* Validity of the task is checked always before and after \
517 execution beacuse the task might have been unregistered \
518 in the callback function, ie. it is not valid anymore. */ \
520 if (task->valid && schedule->fd_list.fd[i] != -1) { \
521 /* Task ready for reading */ \
522 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) { \
523 silc_mutex_unlock(schedule->lock); \
524 task->callback(queue, SILC_TASK_READ, \
526 silc_mutex_lock(schedule->lock); \
530 if (task->valid && schedule->fd_list.fd[i] != -1) { \
531 /* Task ready for writing */ \
532 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) { \
533 silc_mutex_unlock(schedule->lock); \
534 task->callback(queue, SILC_TASK_WRITE, \
536 silc_mutex_lock(schedule->lock); \
540 if (!task->valid) { \
541 /* Invalid (unregistered) tasks are removed from the \
543 if (queue->task == task->next) { \
544 silc_task_remove(queue, task); \
549 silc_task_remove(queue, task->prev); \
553 /* Break if there isn't more tasks in the queue */ \
554 if (queue->task == task->next) \
562 silc_mutex_unlock(schedule->lock); \
566 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
568 struct timeval timeout;
572 struct timeval curtime;
575 SILC_LOG_DEBUG(("In scheduler loop"));
577 /* If the task queues aren't initialized or we aren't valid anymore
579 if ((!schedule->fd_queue && !schedule->timeout_queue
580 && !schedule->generic_queue) || schedule->valid == FALSE) {
581 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
585 /* Clear everything */
586 FD_ZERO(&schedule->in);
587 FD_ZERO(&schedule->out);
588 schedule->max_fd = -1;
591 /* Calculate next timeout for select(). This is the timeout value
592 when at earliest some of the timeout tasks expire. */
593 SILC_SCHEDULE_SELECT_TIMEOUT;
595 silc_mutex_lock(schedule->lock);
597 /* Add the file descriptors to the fd sets. These are the non-timeout
598 tasks. The select() listens to these file descriptors. */
599 SILC_SCHEDULE_SELECT_TASKS;
601 if (schedule->max_fd == -1 && !schedule->timeout)
604 if (schedule->timeout) {
605 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
606 schedule->timeout->tv_usec));
609 if (timeout_usecs >= 0) {
611 timeout.tv_usec = timeout_usecs;
612 schedule->timeout = &timeout;
615 silc_mutex_unlock(schedule->lock);
617 /* This is the main select(). The program blocks here until some
618 of the selected file descriptors change status or the selected
620 SILC_LOG_DEBUG(("Select"));
621 ret = silc_select(schedule->max_fd + 1, &schedule->in,
622 &schedule->out, 0, schedule->timeout);
629 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
633 SILC_LOG_DEBUG(("Running timeout tasks"));
634 silc_gettimeofday(&curtime);
635 SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
638 /* There is some data available now */
639 SILC_LOG_DEBUG(("Running non-timeout tasks"));
640 SILC_SCHEDULE_RUN_TASKS;
642 SILC_SCHEDULE_RUN_GENERIC_TASKS;
649 /* The SILC scheduler. This is actually the main routine in SILC programs.
650 When this returns the program is to be ended. Before this function can
651 be called, one must call silc_schedule_init function. */
653 void silc_schedule(SilcSchedule schedule)
655 SILC_LOG_DEBUG(("Running scheduler"));
657 if (schedule->valid == FALSE) {
658 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
662 /* Start the scheduler loop */
663 while (silc_schedule_one(schedule, -1))
667 /* Wakes up the scheduler. This is used only in multi-threaded
668 environments where threads may add new tasks or remove old tasks
669 from task queues. This is called to wake up the scheduler in the
670 main thread so that it detects the changes in the task queues.
671 If threads support is not compiled in this function has no effect.
672 Implementation of this function is platform specific. */
674 void silc_schedule_wakeup(SilcSchedule schedule)
677 SILC_LOG_DEBUG(("Wakeup scheduler"));
678 silc_mutex_lock(schedule->lock);
679 silc_schedule_wakeup_internal(schedule->wakeup);
680 silc_mutex_unlock(schedule->lock);