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 /* System specific routines. Implemented under unix/ and win32/. */
29 /* System specific select(). */
30 int silc_select(int n, fd_set *readfds, fd_set *writefds,
31 fd_set *exceptfds, struct timeval *timeout);
33 /* Initializes the wakeup of the scheduler. In multi-threaded environment
34 the scheduler needs to be wakenup when tasks are added or removed from
35 the task queues. This will initialize the wakeup for the scheduler.
36 Any tasks that needs to be registered must be registered to the `queue'.
37 It is guaranteed that the scheduler will automatically free any
38 registered tasks in this queue. This is system specific routine. */
39 void *silc_schedule_wakeup_init(void *queue);
41 /* Uninitializes the system specific wakeup. */
42 void silc_schedule_wakeup_uninit(void *context);
44 /* Wakes up the scheduler. This is platform specific routine */
45 void silc_schedule_wakeup_internal(void *context);
47 /* Structure holding list of file descriptors, scheduler is supposed to
48 be listenning. The max_fd field is the maximum number of possible file
49 descriptors in the list. This value is set at the initialization
50 of the scheduler and it usually is the maximum number of connections
59 SILC Scheduler structure.
61 This is the actual schedule object in SILC. Both SILC client and server
62 uses this same scheduler. Actually, this scheduler could be used by any
63 program needing scheduling.
65 Following short description of the fields:
67 SilcTaskQueue fd_queue
69 Task queue hook for non-timeout tasks. Usually this means that these
70 tasks perform different kind of I/O on file descriptors. File
71 descriptors are usually network sockets but they actually can be
72 any file descriptors. This hook is initialized in silc_schedule_init
73 function. Timeout tasks should not be added to this queue because
74 they will never expire.
76 SilcTaskQueue timeout_queue
78 Task queue hook for timeout tasks. This hook is reserved specificly
79 for tasks with timeout. Non-timeout tasks should not be added to this
80 queue because they will never get scheduled. This hook is also
81 initialized in silc_schedule_init function.
83 SilcTaskQueue generic_queue
85 Task queue hook for generic tasks. This hook is reserved specificly
86 for generic tasks, tasks that apply to all file descriptors, except
87 to those that have specificly registered a non-timeout task. This hook
88 is also initialized in silc_schedule_init function.
90 SilcScheduleFdList fd_list
92 List of file descriptors the scheduler is supposed to be listenning.
93 This is updated internally.
95 struct timeval *timeout;
97 Pointer to the schedules next timeout. Value of this timeout is
98 automatically updated in the silc_schedule function.
102 Marks validity of the scheduler. This is a boolean value. When this
103 is false the scheduler is terminated and the program will end. This
104 set to true when the scheduler is initialized with silc_schedule_init
110 File descriptor sets for select(). These are automatically managed
111 by the scheduler and should not be touched otherwise.
115 Number of maximum file descriptors for select(). This, as well, is
116 managed automatically by the scheduler and should be considered to
117 be read-only field otherwise.
121 System specific wakeup context. On multi-threaded environments the
122 scheduler needs to be wakenup (in the thread) when tasks are added
123 or removed. This is initialized by silc_schedule_wakeup_init.
126 struct SilcScheduleStruct {
127 SilcTaskQueue fd_queue;
128 SilcTaskQueue timeout_queue;
129 SilcTaskQueue generic_queue;
130 SilcScheduleFdList fd_list;
131 struct timeval *timeout;
137 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 silc_mutex_lock(schedule->lock);
245 schedule->valid = FALSE;
246 silc_mutex_unlock(schedule->lock);
249 /* Sets a file descriptor to be listened by select() in scheduler. One can
250 call this directly if wanted. This can be called multiple times for
251 one file descriptor to set different iomasks. */
253 void silc_schedule_set_listen_fd(SilcSchedule schedule, int fd, uint32 iomask)
255 silc_mutex_lock(schedule->lock);
257 schedule->fd_list.fd[fd] = iomask;
259 if (fd > schedule->fd_list.last_fd)
260 schedule->fd_list.last_fd = fd;
262 silc_mutex_unlock(schedule->lock);
265 /* Removes a file descriptor from listen list. */
267 void silc_schedule_unset_listen_fd(SilcSchedule schedule, int fd)
269 silc_mutex_lock(schedule->lock);
271 schedule->fd_list.fd[fd] = -1;
273 if (fd == schedule->fd_list.last_fd) {
276 for (i = fd; i >= 0; i--)
277 if (schedule->fd_list.fd[i] != -1)
280 schedule->fd_list.last_fd = i < 0 ? 0 : i;
283 silc_mutex_unlock(schedule->lock);
286 /* Executes tasks matching the file descriptor set by select(). The task
287 remains on the task queue after execution. Invalid tasks are removed
288 here from the task queue. This macro is used by silc_schedule function.
289 We don't have to care about the tasks priority here because the tasks
290 are sorted in their priority order already at the registration phase. */
291 /* This must be called holding the schedule->lock and the
292 schedule->fd_queue->lock. */
294 #define SILC_SCHEDULE_RUN_TASKS \
296 queue = schedule->fd_queue; \
297 if (queue && queue->valid == TRUE && queue->task) { \
298 task = queue->task; \
300 /* Walk thorugh all tasks in the particular task queue and \
301 execute the callback functions of those tasks matching the \
302 fd set by select(). */ \
304 /* Validity of the task is checked always before and after \
305 execution beacuse the task might have been unregistered \
306 in the callback function, ie. it is not valid anymore. */ \
309 /* Task ready for reading */ \
310 if ((FD_ISSET(task->fd, &schedule->in)) && \
311 (task->iomask & (1L << SILC_TASK_READ))) { \
312 silc_mutex_unlock(queue->lock); \
313 silc_mutex_unlock(schedule->lock); \
314 task->callback(queue, SILC_TASK_READ, task->context, task->fd); \
315 silc_mutex_lock(schedule->lock); \
316 silc_mutex_lock(queue->lock); \
322 /* Task ready for writing */ \
323 if ((FD_ISSET(task->fd, &schedule->out)) && \
324 (task->iomask & (1L << SILC_TASK_WRITE))) { \
325 silc_mutex_unlock(queue->lock); \
326 silc_mutex_unlock(schedule->lock); \
327 task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
328 silc_mutex_lock(schedule->lock); \
329 silc_mutex_lock(queue->lock); \
334 if (!task->valid) { \
335 /* Invalid (unregistered) tasks are removed from the \
337 if (queue->task == task->next) { \
338 silc_task_remove(queue, task); \
343 silc_task_remove(queue, task->prev); \
347 /* Break if there isn't more tasks in the queue */ \
348 if (queue->task == task->next) \
356 /* Selects tasks to be listened by select(). These are the non-timeout
357 tasks. This checks the scheduler's fd list. This macro is used by
358 silc_schedule function. */
359 /* This must be called holding schedule->lock. */
361 #define SILC_SCHEDULE_SELECT_TASKS \
363 for (i = 0; i <= schedule->fd_list.last_fd; i++) { \
364 if (schedule->fd_list.fd[i] != -1) { \
366 /* Set the max fd value for select() to listen */ \
367 if (i > schedule->max_fd) \
368 schedule->max_fd = i; \
370 /* Add tasks for reading */ \
371 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) \
372 FD_SET(i, &schedule->in); \
374 /* Add tasks for writing */ \
375 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
376 FD_SET(i, &schedule->out); \
381 /* Executes all tasks whose timeout has expired. The task is removed from
382 the task queue after the callback function has returned. Also, invalid
383 tasks are removed here. The current time must be get before calling this
384 macro. This macro is used by silc_schedule function. We don't have to
385 care about priorities because tasks are already sorted in their priority
386 order at the registration phase. */
387 /* This must be called with holding the schedule->lock and the
388 schedule->timeout_queue->lock */
390 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS \
392 queue = schedule->timeout_queue; \
393 if (queue && queue->valid == TRUE && queue->task) { \
394 task = queue->task; \
396 /* Walk thorugh all tasks in the particular task queue \
397 and run all the expired tasks. */ \
399 /* Execute the task if the timeout has expired */ \
400 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
402 /* Task ready for reading */ \
404 if ((task->iomask & (1L << SILC_TASK_READ))) { \
405 silc_mutex_unlock(queue->lock); \
406 silc_mutex_unlock(schedule->lock); \
407 task->callback(queue, SILC_TASK_READ, \
408 task->context, task->fd); \
409 silc_mutex_lock(schedule->lock); \
410 silc_mutex_lock(queue->lock); \
414 /* Task ready for writing */ \
416 if ((task->iomask & (1L << SILC_TASK_WRITE))) { \
417 silc_mutex_unlock(queue->lock); \
418 silc_mutex_unlock(schedule->lock); \
419 task->callback(queue, SILC_TASK_WRITE, \
420 task->context, task->fd); \
421 silc_mutex_lock(schedule->lock); \
422 silc_mutex_lock(queue->lock); \
426 /* Break if there isn't more tasks in the queue */ \
427 if (queue->task == task->next) { \
428 /* Remove the task from queue */ \
429 silc_task_remove(queue, task); \
435 /* Remove the task from queue */ \
436 silc_task_remove(queue, task->prev); \
438 /* The timeout hasn't expired, check for next one */ \
440 /* Break if there isn't more tasks in the queue */ \
441 if (queue->task == task->next) \
450 /* Calculates next timeout for select(). This is the timeout value
451 when at earliest some of the timeout tasks expire. If this is in the
452 past, they will be run now. This macro is used by the silc_schedule
454 /* This must be called with holding the schedule->lock and the
455 schedule->timeout_queue->lock */
457 #define SILC_SCHEDULE_SELECT_TIMEOUT \
459 if (schedule->timeout_queue && schedule->timeout_queue->valid == TRUE) { \
460 queue = schedule->timeout_queue; \
463 /* Get the current time */ \
464 silc_gettimeofday(&curtime); \
465 schedule->timeout = NULL; \
467 /* First task in the task queue has always the smallest timeout. */ \
468 task = queue->task; \
470 if (task && task->valid == TRUE) { \
472 /* If the timeout is in past, we will run the task and all other \
473 timeout tasks from the past. */ \
474 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
475 SILC_SCHEDULE_RUN_TIMEOUT_TASKS; \
477 /* The task(s) has expired and doesn't exist on the task queue \
478 anymore. We continue with new timeout. */ \
479 queue = schedule->timeout_queue; \
480 task = queue->task; \
481 if (task == NULL || task->valid == FALSE) \
486 /* Calculate the next timeout for select() */ \
487 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec; \
488 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
489 if (queue->timeout.tv_sec < 0) \
490 queue->timeout.tv_sec = 0; \
492 /* We wouldn't want to go under zero, check for it. */ \
493 if (queue->timeout.tv_usec < 0) { \
494 queue->timeout.tv_sec -= 1; \
495 if (queue->timeout.tv_sec < 0) \
496 queue->timeout.tv_sec = 0; \
497 queue->timeout.tv_usec += 1000000L; \
500 /* We've got the timeout value */ \
503 /* Task is not valid, remove it and try next one. */ \
504 silc_task_remove(queue, task); \
505 task = queue->task; \
506 if (queue->task == NULL) \
510 /* Save the timeout */ \
512 schedule->timeout = &queue->timeout; \
516 /* Execute generic tasks. These are executed only and only if for the
517 specific fd there wasn't other non-timeout tasks. This checks the earlier
518 set fd list, thus the generic tasks apply to all specified fd's. All the
519 generic tasks are executed at once. */
520 /* This must be called holding the schedule->lock and the
521 schedule->generic_queue->lock. */
523 #define SILC_SCHEDULE_RUN_GENERIC_TASKS \
525 if (is_run == FALSE) { \
526 SILC_LOG_DEBUG(("Running generic tasks")); \
527 for (i = 0; i <= schedule->fd_list.last_fd; i++) \
528 if (schedule->fd_list.fd[i] != -1) { \
530 /* Check whether this fd is select()ed. */ \
531 if ((FD_ISSET(i, &schedule->in)) || (FD_ISSET(i, &schedule->out))) { \
533 /* It was selected. Now find the tasks from task queue and execute \
534 all generic tasks. */ \
535 if (schedule->generic_queue && schedule->generic_queue->valid) { \
536 queue = schedule->generic_queue; \
541 task = queue->task; \
544 /* Validity of the task is checked always before and after \
545 execution beacuse the task might have been unregistered \
546 in the callback function, ie. it is not valid anymore. */ \
548 if (task->valid && schedule->fd_list.fd[i] != -1) { \
549 /* Task ready for reading */ \
550 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) { \
551 silc_mutex_unlock(queue->lock); \
552 silc_mutex_unlock(schedule->lock); \
553 task->callback(queue, SILC_TASK_READ, \
555 silc_mutex_lock(schedule->lock); \
556 silc_mutex_lock(queue->lock); \
560 if (task->valid && schedule->fd_list.fd[i] != -1) { \
561 /* Task ready for writing */ \
562 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) { \
563 silc_mutex_unlock(queue->lock); \
564 silc_mutex_unlock(schedule->lock); \
565 task->callback(queue, SILC_TASK_WRITE, \
567 silc_mutex_lock(schedule->lock); \
568 silc_mutex_lock(queue->lock); \
572 if (!task->valid) { \
573 /* Invalid (unregistered) tasks are removed from the \
575 if (queue->task == task->next) { \
576 silc_task_remove(queue, task); \
581 silc_task_remove(queue, task->prev); \
585 /* Break if there isn't more tasks in the queue */ \
586 if (queue->task == task->next) \
597 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
599 struct timeval timeout;
603 struct timeval curtime;
606 SILC_LOG_DEBUG(("In scheduler loop"));
608 if (!schedule->is_locked)
609 silc_mutex_lock(schedule->lock);
611 /* If the task queues aren't initialized or we aren't valid anymore
613 if ((!schedule->fd_queue && !schedule->timeout_queue
614 && !schedule->generic_queue) || schedule->valid == FALSE) {
615 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
616 if (!schedule->is_locked)
617 silc_mutex_unlock(schedule->lock);
621 /* Clear everything */
622 FD_ZERO(&schedule->in);
623 FD_ZERO(&schedule->out);
624 schedule->max_fd = -1;
627 /* Calculate next timeout for silc_select(). This is the timeout value
628 when at earliest some of the timeout tasks expire. */
629 silc_mutex_lock(schedule->timeout_queue->lock);
630 SILC_SCHEDULE_SELECT_TIMEOUT;
631 silc_mutex_unlock(schedule->timeout_queue->lock);
633 /* Add the file descriptors to the fd sets. These are the non-timeout
634 tasks. The silc_select() listens to these file descriptors. */
635 SILC_SCHEDULE_SELECT_TASKS;
637 if (schedule->max_fd == -1 && !schedule->timeout) {
638 if (!schedule->is_locked)
639 silc_mutex_unlock(schedule->lock);
643 if (schedule->timeout) {
644 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
645 schedule->timeout->tv_usec));
648 if (timeout_usecs >= 0) {
650 timeout.tv_usec = timeout_usecs;
651 schedule->timeout = &timeout;
654 silc_mutex_unlock(schedule->lock);
656 /* This is the main select(). The program blocks here until some
657 of the selected file descriptors change status or the selected
659 SILC_LOG_DEBUG(("Select"));
660 ret = silc_select(schedule->max_fd + 1, &schedule->in,
661 &schedule->out, 0, schedule->timeout);
663 silc_mutex_lock(schedule->lock);
670 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
674 SILC_LOG_DEBUG(("Running timeout tasks"));
675 silc_mutex_lock(schedule->timeout_queue->lock);
676 silc_gettimeofday(&curtime);
677 SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
678 silc_mutex_unlock(schedule->timeout_queue->lock);
681 /* There is some data available now */
682 SILC_LOG_DEBUG(("Running non-timeout tasks"));
683 silc_mutex_lock(schedule->fd_queue->lock);
684 SILC_SCHEDULE_RUN_TASKS;
685 silc_mutex_unlock(schedule->fd_queue->lock);
687 silc_mutex_lock(schedule->generic_queue->lock);
688 SILC_SCHEDULE_RUN_GENERIC_TASKS;
689 silc_mutex_unlock(schedule->generic_queue->lock);
693 if (!schedule->is_locked)
694 silc_mutex_unlock(schedule->lock);
699 /* The SILC scheduler. This is actually the main routine in SILC programs.
700 When this returns the program is to be ended. Before this function can
701 be called, one must call silc_schedule_init function. */
703 void silc_schedule(SilcSchedule schedule)
705 SILC_LOG_DEBUG(("Running scheduler"));
707 if (schedule->valid == FALSE) {
708 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
712 silc_mutex_lock(schedule->lock);
713 schedule->is_locked = TRUE;
715 /* Start the scheduler loop */
716 while (silc_schedule_one(schedule, -1))
719 silc_mutex_unlock(schedule->lock);
722 /* Wakes up the scheduler. This is used only in multi-threaded
723 environments where threads may add new tasks or remove old tasks
724 from task queues. This is called to wake up the scheduler in the
725 main thread so that it detects the changes in the task queues.
726 If threads support is not compiled in this function has no effect.
727 Implementation of this function is platform specific. */
729 void silc_schedule_wakeup(SilcSchedule schedule)
732 SILC_LOG_DEBUG(("Wakeup scheduler"));
733 silc_mutex_lock(schedule->lock);
734 silc_schedule_wakeup_internal(schedule->wakeup);
735 silc_mutex_unlock(schedule->lock);