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 /* System specific implementation of the select. */
25 int silc_select(int n, fd_set *readfds, fd_set *writefds,
26 fd_set *exceptfds, struct timeval *timeout);
28 /* Structure holding list of file descriptors, scheduler is supposed to
29 be listenning. The max_fd field is the maximum number of possible file
30 descriptors in the list. This value is set at the initialization
31 of the scheduler and it usually is the maximum number of connections
40 SILC Scheduler structure.
42 This is the actual schedule object in SILC. Both SILC client and server
43 uses this same scheduler. Actually, this scheduler could be used by any
44 program needing scheduling.
46 Following short description of the fields:
48 SilcTaskQueue fd_queue
50 Task queue hook for non-timeout tasks. Usually this means that these
51 tasks perform different kind of I/O on file descriptors. File
52 descriptors are usually network sockets but they actually can be
53 any file descriptors. This hook is initialized in silc_schedule_init
54 function. Timeout tasks should not be added to this queue because
55 they will never expire.
57 SilcTaskQueue timeout_queue
59 Task queue hook for timeout tasks. This hook is reserved specificly
60 for tasks with timeout. Non-timeout tasks should not be added to this
61 queue because they will never get scheduled. This hook is also
62 initialized in silc_schedule_init function.
64 SilcTaskQueue generic_queue
66 Task queue hook for generic tasks. This hook is reserved specificly
67 for generic tasks, tasks that apply to all file descriptors, except
68 to those that have specificly registered a non-timeout task. This hook
69 is also initialized in silc_schedule_init function.
71 SilcScheduleFdList fd_list
73 List of file descriptors the scheduler is supposed to be listenning.
74 This is updated internally.
76 struct timeval *timeout;
78 Pointer to the schedules next timeout. Value of this timeout is
79 automatically updated in the silc_schedule function.
83 Marks validity of the scheduler. This is a boolean value. When this
84 is false the scheduler is terminated and the program will end. This
85 set to true when the scheduler is initialized with silc_schedule_init
91 File descriptor sets for select(). These are automatically managed
92 by the scheduler and should not be touched otherwise.
96 Number of maximum file descriptors for select(). This, as well, is
97 managed automatically by the scheduler and should be considered to
98 be read-only field otherwise.
101 struct SilcScheduleStruct {
102 SilcTaskQueue fd_queue;
103 SilcTaskQueue timeout_queue;
104 SilcTaskQueue generic_queue;
105 SilcScheduleFdList fd_list;
106 struct timeval *timeout;
113 /* Initializes the scheduler. Sets the non-timeout task queue hook and
114 the timeout task queue hook. This must be called before the scheduler
115 is able to work. This will allocate the queue pointers if they are
116 not allocated. Returns the scheduler context that must be freed by
117 the silc_schedule_uninit function. */
119 SilcSchedule silc_schedule_init(SilcTaskQueue *fd_queue,
120 SilcTaskQueue *timeout_queue,
121 SilcTaskQueue *generic_queue,
124 SilcSchedule schedule;
127 SILC_LOG_DEBUG(("Initializing scheduler"));
129 schedule = silc_calloc(1, sizeof(*schedule));
131 /* Register the task queues if they are not registered already. In SILC
132 we have by default three task queues. One task queue for non-timeout
133 tasks which perform different kind of I/O on file descriptors, timeout
134 task queue for timeout tasks, and, generic non-timeout task queue whose
135 tasks apply to all connections. */
137 silc_task_queue_alloc(schedule, fd_queue, TRUE);
139 silc_task_queue_alloc(schedule, timeout_queue, TRUE);
141 silc_task_queue_alloc(schedule, generic_queue, TRUE);
143 /* Initialize the scheduler */
144 schedule->fd_queue = *fd_queue;
145 schedule->timeout_queue = *timeout_queue;
146 schedule->generic_queue = *generic_queue;
147 schedule->fd_list.fd = silc_calloc(max_fd, sizeof(*schedule->fd_list.fd));
148 schedule->fd_list.last_fd = 0;
149 schedule->fd_list.max_fd = max_fd;
150 schedule->timeout = NULL;
151 schedule->valid = TRUE;
152 FD_ZERO(&schedule->in);
153 FD_ZERO(&schedule->out);
154 schedule->max_fd = -1;
155 for (i = 0; i < max_fd; i++)
156 schedule->fd_list.fd[i] = -1;
161 /* Uninitializes the schedule. This is called when the program is ready
162 to end. This removes all tasks and task queues. Returns FALSE if the
163 scheduler could not be uninitialized. This happens when the scheduler
164 is still valid and silc_schedule_stop has not been called. */
166 bool silc_schedule_uninit(SilcSchedule schedule)
169 SILC_LOG_DEBUG(("Uninitializing scheduler"));
171 if (schedule->valid == TRUE)
174 /* Unregister all tasks */
175 if (schedule->fd_queue)
176 silc_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
177 if (schedule->timeout_queue)
178 silc_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
179 if (schedule->generic_queue)
180 silc_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
182 /* Unregister all task queues */
183 if (schedule->fd_queue)
184 silc_task_queue_free(schedule->fd_queue);
185 if (schedule->timeout_queue)
186 silc_task_queue_free(schedule->timeout_queue);
187 if (schedule->generic_queue)
188 silc_task_queue_free(schedule->generic_queue);
190 /* Clear the fd list */
191 if (schedule->fd_list.fd) {
192 memset(schedule->fd_list.fd, -1, schedule->fd_list.max_fd);
193 silc_free(schedule->fd_list.fd);
199 /* Stops the schedule even if it is not supposed to be stopped yet.
200 After calling this, one should call silc_schedule_uninit (after the
201 silc_schedule has returned). */
203 void silc_schedule_stop(SilcSchedule schedule)
205 SILC_LOG_DEBUG(("Stopping scheduler"));
207 if (schedule->valid == TRUE)
208 schedule->valid = FALSE;
211 /* Sets a file descriptor to be listened by select() in scheduler. One can
212 call this directly if wanted. This can be called multiple times for
213 one file descriptor to set different iomasks. */
215 void silc_schedule_set_listen_fd(SilcSchedule schedule, int fd, uint32 iomask)
217 schedule->fd_list.fd[fd] = iomask;
219 if (fd > schedule->fd_list.last_fd)
220 schedule->fd_list.last_fd = fd;
223 /* Removes a file descriptor from listen list. */
225 void silc_schedule_unset_listen_fd(SilcSchedule schedule, int fd)
227 schedule->fd_list.fd[fd] = -1;
229 if (fd == schedule->fd_list.last_fd) {
232 for (i = fd; i >= 0; i--)
233 if (schedule->fd_list.fd[i] != -1)
236 schedule->fd_list.last_fd = i < 0 ? 0 : i;
240 /* Executes tasks matching the file descriptor set by select(). The task
241 remains on the task queue after execution. Invalid tasks are removed
242 here from the task queue. This macro is used by silc_schedule function.
243 We don't have to care about the tasks priority here because the tasks
244 are sorted in their priority order already at the registration phase. */
246 #define SILC_SCHEDULE_RUN_TASKS \
248 queue = schedule->fd_queue; \
249 if (queue && queue->valid == TRUE && queue->task) { \
250 task = queue->task; \
252 /* Walk thorugh all tasks in the particular task queue and \
253 execute the callback functions of those tasks matching the \
254 fd set by select(). */ \
256 /* Validity of the task is checked always before and after \
257 execution beacuse the task might have been unregistered \
258 in the callback function, ie. it is not valid anymore. */ \
261 /* Task ready for reading */ \
262 if ((FD_ISSET(task->fd, &schedule->in)) && \
263 (task->iomask & (1L << SILC_TASK_READ))) { \
264 task->callback(queue, SILC_TASK_READ, task->context, task->fd); \
270 /* Task ready for writing */ \
271 if ((FD_ISSET(task->fd, &schedule->out)) && \
272 (task->iomask & (1L << SILC_TASK_WRITE))) { \
273 task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
278 if (!task->valid) { \
279 /* Invalid (unregistered) tasks are removed from the \
281 if (queue->task == task->next) { \
282 silc_task_remove(queue, task); \
287 silc_task_remove(queue, task->prev); \
291 /* Break if there isn't more tasks in the queue */ \
292 if (queue->task == task->next) \
300 /* Selects tasks to be listened by select(). These are the non-timeout
301 tasks. This checks the scheduler's fd list. This macro is used by
302 silc_schedule function. */
304 #define SILC_SCHEDULE_SELECT_TASKS \
306 for (i = 0; i <= schedule->fd_list.last_fd; i++) { \
307 if (schedule->fd_list.fd[i] != -1) { \
309 /* Set the max fd value for select() to listen */ \
310 if (i > schedule->max_fd) \
311 schedule->max_fd = i; \
313 /* Add tasks for reading */ \
314 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) \
315 FD_SET(i, &schedule->in); \
317 /* Add tasks for writing */ \
318 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
319 FD_SET(i, &schedule->out); \
324 /* Executes all tasks whose timeout has expired. The task is removed from
325 the task queue after the callback function has returned. Also, invalid
326 tasks are removed here. The current time must be get before calling this
327 macro. This macro is used by silc_schedule function. We don't have to
328 care about priorities because tasks are already sorted in their priority
329 order at the registration phase. */
331 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS \
333 queue = schedule->timeout_queue; \
334 if (queue && queue->valid == TRUE && queue->task) { \
335 task = queue->task; \
337 /* Walk thorugh all tasks in the particular task queue \
338 and run all the expired tasks. */ \
340 /* Execute the task if the timeout has expired */ \
341 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
343 /* Task ready for reading */ \
345 if ((task->iomask & (1L << SILC_TASK_READ))) \
346 task->callback(queue, SILC_TASK_READ, \
347 task->context, task->fd); \
350 /* Task ready for writing */ \
352 if ((task->iomask & (1L << SILC_TASK_WRITE))) \
353 task->callback(queue, SILC_TASK_WRITE, \
354 task->context, task->fd); \
357 /* Break if there isn't more tasks in the queue */ \
358 if (queue->task == task->next) { \
359 /* Remove the task from queue */ \
360 silc_task_remove(queue, task); \
366 /* Remove the task from queue */ \
367 silc_task_remove(queue, task->prev); \
369 /* The timeout hasn't expired, check for next one */ \
371 /* Break if there isn't more tasks in the queue */ \
372 if (queue->task == task->next) \
381 /* Calculates next timeout for select(). This is the timeout value
382 when at earliest some of the timeout tasks expire. If this is in the
383 past, they will be run now. This macro is used by the silc_schedule
386 #define SILC_SCHEDULE_SELECT_TIMEOUT \
388 if (schedule->timeout_queue && schedule->timeout_queue->valid == TRUE) { \
389 queue = schedule->timeout_queue; \
392 /* Get the current time */ \
393 silc_gettimeofday(&curtime); \
394 schedule->timeout = NULL; \
396 /* First task in the task queue has always the smallest timeout. */ \
397 task = queue->task; \
399 if (task && task->valid == TRUE) { \
401 /* If the timeout is in past, we will run the task and all other \
402 timeout tasks from the past. */ \
403 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
404 SILC_SCHEDULE_RUN_TIMEOUT_TASKS; \
406 /* The task(s) has expired and doesn't exist on the task queue \
407 anymore. We continue with new timeout. */ \
408 queue = schedule->timeout_queue; \
409 task = queue->task; \
410 if (task == NULL || task->valid == FALSE) \
415 /* Calculate the next timeout for select() */ \
416 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec; \
417 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
418 if (queue->timeout.tv_sec < 0) \
419 queue->timeout.tv_sec = 0; \
421 /* We wouldn't want to go under zero, check for it. */ \
422 if (queue->timeout.tv_usec < 0) { \
423 queue->timeout.tv_sec -= 1; \
424 if (queue->timeout.tv_sec < 0) \
425 queue->timeout.tv_sec = 0; \
426 queue->timeout.tv_usec += 1000000L; \
429 /* We've got the timeout value */ \
432 /* Task is not valid, remove it and try next one. */ \
433 silc_task_remove(queue, task); \
434 task = queue->task; \
435 if (queue->task == NULL) \
439 /* Save the timeout */ \
441 schedule->timeout = &queue->timeout; \
445 /* Execute generic tasks. These are executed only and only if for the
446 specific fd there wasn't other non-timeout tasks. This checks the earlier
447 set fd list, thus the generic tasks apply to all specified fd's. All the
448 generic tasks are executed at once. */
450 #define SILC_SCHEDULE_RUN_GENERIC_TASKS \
452 if (is_run == FALSE) { \
453 SILC_LOG_DEBUG(("Running generic tasks")); \
454 for (i = 0; i <= schedule->fd_list.last_fd; i++) \
455 if (schedule->fd_list.fd[i] != -1) { \
457 /* Check whether this fd is select()ed. */ \
458 if ((FD_ISSET(i, &schedule->in)) || (FD_ISSET(i, &schedule->out))) { \
460 /* It was selected. Now find the tasks from task queue and execute \
461 all generic tasks. */ \
462 if (schedule->generic_queue && schedule->generic_queue->valid) { \
463 queue = schedule->generic_queue; \
468 task = queue->task; \
471 /* Validity of the task is checked always before and after \
472 execution beacuse the task might have been unregistered \
473 in the callback function, ie. it is not valid anymore. */ \
475 if (task->valid && schedule->fd_list.fd[i] != -1) { \
476 /* Task ready for reading */ \
477 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) \
478 task->callback(queue, SILC_TASK_READ, \
482 if (task->valid && schedule->fd_list.fd[i] != -1) { \
483 /* Task ready for writing */ \
484 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
485 task->callback(queue, SILC_TASK_WRITE, \
489 if (!task->valid) { \
490 /* Invalid (unregistered) tasks are removed from the \
492 if (queue->task == task->next) { \
493 silc_task_remove(queue, task); \
498 silc_task_remove(queue, task->prev); \
502 /* Break if there isn't more tasks in the queue */ \
503 if (queue->task == task->next) \
514 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
516 struct timeval timeout;
520 struct timeval curtime;
522 SILC_LOG_DEBUG(("In scheduler loop"));
524 /* If the task queues aren't initialized or we aren't valid anymore
526 if ((!schedule->fd_queue && !schedule->timeout_queue
527 && !schedule->generic_queue) || schedule->valid == FALSE) {
528 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
532 /* Clear everything */
533 FD_ZERO(&schedule->in);
534 FD_ZERO(&schedule->out);
535 schedule->max_fd = -1;
538 /* Calculate next timeout for select(). This is the timeout value
539 when at earliest some of the timeout tasks expire. */
540 SILC_SCHEDULE_SELECT_TIMEOUT;
542 /* Add the file descriptors to the fd sets. These are the non-timeout
543 tasks. The select() listens to these file descriptors. */
544 SILC_SCHEDULE_SELECT_TASKS;
546 if (schedule->max_fd == -1 && !schedule->timeout)
549 if (schedule->timeout) {
550 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
551 schedule->timeout->tv_usec));
554 if (timeout_usecs >= 0) {
556 timeout.tv_usec = timeout_usecs;
557 schedule->timeout = &timeout;
560 /* This is the main select(). The program blocks here until some
561 of the selected file descriptors change status or the selected
563 SILC_LOG_DEBUG(("Select"));
564 switch (silc_select(schedule->max_fd + 1, &schedule->in,
565 &schedule->out, 0, schedule->timeout)) {
570 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
574 SILC_LOG_DEBUG(("Running timeout tasks"));
575 silc_gettimeofday(&curtime);
576 SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
579 /* There is some data available now */
580 SILC_LOG_DEBUG(("Running non-timeout tasks"));
581 SILC_SCHEDULE_RUN_TASKS;
583 SILC_SCHEDULE_RUN_GENERIC_TASKS;
590 /* The SILC scheduler. This is actually the main routine in SILC programs.
591 When this returns the program is to be ended. Before this function can
592 be called, one must call silc_schedule_init function. */
594 void silc_schedule(SilcSchedule schedule)
596 SILC_LOG_DEBUG(("Running scheduler"));
598 if (schedule->valid == FALSE) {
599 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
603 /* Start the scheduler loop */
604 while (silc_schedule_one(schedule, -1))