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 /* Structure holding list of file descriptors, scheduler is supposed to
25 be listenning. The max_fd field is the maximum number of possible file
26 descriptors in the list. This value is set at the initialization
27 of the scheduler and it usually is the maximum number of connections
36 SILC Unix Scheduler structure.
38 This is the actual schedule object in SILC. Both SILC client and server
39 uses this same scheduler. Actually, this scheduler could be used by any
40 program needing scheduling.
42 Following short description of the fields:
44 SilcTaskQueue fd_queue
46 Task queue hook for non-timeout tasks. Usually this means that these
47 tasks perform different kind of I/O on file descriptors. File
48 descriptors are usually network sockets but they actually can be
49 any file descriptors. This hook is initialized in silc_schedule_init
50 function. Timeout tasks should not be added to this queue because
51 they will never expire.
53 SilcTaskQueue timeout_queue
55 Task queue hook for timeout tasks. This hook is reserved specificly
56 for tasks with timeout. Non-timeout tasks should not be added to this
57 queue because they will never get scheduled. This hook is also
58 initialized in silc_schedule_init function.
60 SilcTaskQueue generic_queue
62 Task queue hook for generic tasks. This hook is reserved specificly
63 for generic tasks, tasks that apply to all file descriptors, except
64 to those that have specificly registered a non-timeout task. This hook
65 is also initialized in silc_schedule_init function.
67 SilcScheduleFdList fd_list
69 List of file descriptors the scheduler is supposed to be listenning.
70 This is updated internally.
72 struct timeval *timeout;
74 Pointer to the schedules next timeout. Value of this timeout is
75 automatically updated in the silc_schedule function.
79 Marks validity of the scheduler. This is a boolean value. When this
80 is false the scheduler is terminated and the program will end. This
81 set to true when the scheduler is initialized with silc_schedule_init
87 File descriptor sets for select(). These are automatically managed
88 by the scheduler and should not be touched otherwise.
92 Number of maximum file descriptors for select(). This, as well, is
93 managed automatically by the scheduler and should be considered to
94 be read-only field otherwise.
97 struct SilcScheduleStruct {
98 SilcTaskQueue fd_queue;
99 SilcTaskQueue timeout_queue;
100 SilcTaskQueue generic_queue;
101 SilcScheduleFdList fd_list;
102 struct timeval *timeout;
109 /* Initializes the scheduler. Sets the non-timeout task queue hook and
110 the timeout task queue hook. This must be called before the scheduler
111 is able to work. This will allocate the queue pointers if they are
112 not allocated. Returns the scheduler context that must be freed by
113 the silc_schedule_uninit function. */
115 SilcSchedule silc_schedule_init(SilcTaskQueue *fd_queue,
116 SilcTaskQueue *timeout_queue,
117 SilcTaskQueue *generic_queue,
120 SilcSchedule schedule;
123 SILC_LOG_DEBUG(("Initializing scheduler"));
125 schedule = silc_calloc(1, sizeof(*schedule));
127 /* Register the task queues if they are not registered already. In SILC
128 we have by default three task queues. One task queue for non-timeout
129 tasks which perform different kind of I/O on file descriptors, timeout
130 task queue for timeout tasks, and, generic non-timeout task queue whose
131 tasks apply to all connections. */
133 silc_task_queue_alloc(schedule, fd_queue, TRUE);
135 silc_task_queue_alloc(schedule, timeout_queue, TRUE);
137 silc_task_queue_alloc(schedule, generic_queue, TRUE);
139 /* Initialize the scheduler */
140 schedule->fd_queue = *fd_queue;
141 schedule->timeout_queue = *timeout_queue;
142 schedule->generic_queue = *generic_queue;
143 schedule->fd_list.fd = silc_calloc(max_fd, sizeof(int));
144 schedule->fd_list.last_fd = 0;
145 schedule->fd_list.max_fd = max_fd;
146 schedule->timeout = NULL;
147 schedule->valid = TRUE;
148 FD_ZERO(&schedule->in);
149 FD_ZERO(&schedule->out);
150 schedule->max_fd = -1;
151 for (i = 0; i < max_fd; i++)
152 schedule->fd_list.fd[i] = -1;
157 /* Uninitializes the schedule. This is called when the program is ready
158 to end. This removes all tasks and task queues. Returns FALSE if the
159 scheduler could not be uninitialized. This happens when the scheduler
160 is still valid and silc_schedule_stop has not been called. */
162 bool silc_schedule_uninit(SilcSchedule schedule)
165 SILC_LOG_DEBUG(("Uninitializing scheduler"));
167 if (schedule->valid == TRUE)
170 /* Unregister all tasks */
171 if (schedule->fd_queue)
172 silc_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
173 if (schedule->timeout_queue)
174 silc_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
175 if (schedule->generic_queue)
176 silc_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
178 /* Unregister all task queues */
179 if (schedule->fd_queue)
180 silc_task_queue_free(schedule->fd_queue);
181 if (schedule->timeout_queue)
182 silc_task_queue_free(schedule->timeout_queue);
183 if (schedule->generic_queue)
184 silc_task_queue_free(schedule->generic_queue);
186 /* Clear the fd list */
187 if (schedule->fd_list.fd) {
188 memset(schedule->fd_list.fd, -1, schedule->fd_list.max_fd);
189 silc_free(schedule->fd_list.fd);
192 memset(&schedule, 'F', sizeof(schedule));
196 /* Stops the schedule even if it is not supposed to be stopped yet.
197 After calling this, one should call silc_schedule_uninit (after the
198 silc_schedule has returned). */
200 void silc_schedule_stop(SilcSchedule schedule)
202 SILC_LOG_DEBUG(("Stopping scheduler"));
204 if (schedule->valid == TRUE)
205 schedule->valid = FALSE;
208 /* Sets a file descriptor to be listened by select() in scheduler. One can
209 call this directly if wanted. This can be called multiple times for
210 one file descriptor to set different iomasks. */
212 void silc_schedule_set_listen_fd(SilcSchedule schedule, int fd, uint32 iomask)
214 schedule->fd_list.fd[fd] = iomask;
216 if (fd > schedule->fd_list.last_fd)
217 schedule->fd_list.last_fd = fd;
220 /* Removes a file descriptor from listen list. */
222 void silc_schedule_unset_listen_fd(SilcSchedule schedule, int fd)
224 schedule->fd_list.fd[fd] = -1;
226 if (fd == schedule->fd_list.last_fd) {
229 for (i = fd; i >= 0; i--)
230 if (schedule->fd_list.fd[i] != -1)
233 schedule->fd_list.last_fd = i < 0 ? 0 : i;
237 /* Executes tasks matching the file descriptor set by select(). The task
238 remains on the task queue after execution. Invalid tasks are removed
239 here from the task queue. This macro is used by silc_schedule function.
240 We don't have to care about the tasks priority here because the tasks
241 are sorted in their priority order already at the registration phase. */
243 #define SILC_SCHEDULE_RUN_TASKS \
245 queue = schedule->fd_queue; \
246 if (queue && queue->valid == TRUE && queue->task) { \
247 task = queue->task; \
249 /* Walk thorugh all tasks in the particular task queue and \
250 execute the callback functions of those tasks matching the \
251 fd set by select(). */ \
253 /* Validity of the task is checked always before and after \
254 execution beacuse the task might have been unregistered \
255 in the callback function, ie. it is not valid anymore. */ \
258 /* Task ready for reading */ \
259 if ((FD_ISSET(task->fd, &schedule->in)) && \
260 (task->iomask & (1L << SILC_TASK_READ))) { \
261 task->callback(queue, SILC_TASK_READ, task->context, task->fd); \
267 /* Task ready for writing */ \
268 if ((FD_ISSET(task->fd, &schedule->out)) && \
269 (task->iomask & (1L << SILC_TASK_WRITE))) { \
270 task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
275 if (!task->valid) { \
276 /* Invalid (unregistered) tasks are removed from the \
278 if (queue->task == task->next) { \
279 silc_task_remove(queue, task); \
284 silc_task_remove(queue, task->prev); \
288 /* Break if there isn't more tasks in the queue */ \
289 if (queue->task == task->next) \
297 /* Selects tasks to be listened by select(). These are the non-timeout
298 tasks. This checks the scheduler's fd list. This macro is used by
299 silc_schedule function. */
301 #define SILC_SCHEDULE_SELECT_TASKS \
303 for (i = 0; i <= schedule->fd_list.last_fd; i++) { \
304 if (schedule->fd_list.fd[i] != -1) { \
306 /* Set the max fd value for select() to listen */ \
307 if (i > schedule->max_fd) \
308 schedule->max_fd = i; \
310 /* Add tasks for reading */ \
311 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) \
312 FD_SET(i, &schedule->in); \
314 /* Add tasks for writing */ \
315 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
316 FD_SET(i, &schedule->out); \
321 /* Executes all tasks whose timeout has expired. The task is removed from
322 the task queue after the callback function has returned. Also, invalid
323 tasks are removed here. The current time must be get before calling this
324 macro. This macro is used by silc_schedule function. We don't have to
325 care about priorities because tasks are already sorted in their priority
326 order at the registration phase. */
328 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS \
330 queue = schedule->timeout_queue; \
331 if (queue && queue->valid == TRUE && queue->task) { \
332 task = queue->task; \
334 /* Walk thorugh all tasks in the particular task queue \
335 and run all the expired tasks. */ \
337 /* Execute the task if the timeout has expired */ \
338 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
340 /* Task ready for reading */ \
342 if ((task->iomask & (1L << SILC_TASK_READ))) \
343 task->callback(queue, SILC_TASK_READ, \
344 task->context, task->fd); \
347 /* Task ready for writing */ \
349 if ((task->iomask & (1L << SILC_TASK_WRITE))) \
350 task->callback(queue, SILC_TASK_WRITE, \
351 task->context, task->fd); \
354 /* Break if there isn't more tasks in the queue */ \
355 if (queue->task == task->next) { \
356 /* Remove the task from queue */ \
357 silc_task_remove(queue, task); \
363 /* Remove the task from queue */ \
364 silc_task_remove(queue, task->prev); \
366 /* The timeout hasn't expired, check for next one */ \
368 /* Break if there isn't more tasks in the queue */ \
369 if (queue->task == task->next) \
378 /* Calculates next timeout for select(). This is the timeout value
379 when at earliest some of the timeout tasks expire. If this is in the
380 past, they will be run now. This macro is used by the silc_schedule
383 #define SILC_SCHEDULE_SELECT_TIMEOUT \
385 if (schedule->timeout_queue && schedule->timeout_queue->valid == TRUE) { \
386 queue = schedule->timeout_queue; \
389 /* Get the current time */ \
390 gettimeofday(&curtime, NULL); \
391 schedule->timeout = NULL; \
393 /* First task in the task queue has always the smallest timeout. */ \
394 task = queue->task; \
396 if (task && task->valid == TRUE) { \
398 /* If the timeout is in past, we will run the task and all other \
399 timeout tasks from the past. */ \
400 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
401 SILC_SCHEDULE_RUN_TIMEOUT_TASKS; \
403 /* The task(s) has expired and doesn't exist on the task queue \
404 anymore. We continue with new timeout. */ \
405 queue = schedule->timeout_queue; \
406 task = queue->task; \
407 if (task == NULL || task->valid == FALSE) \
412 /* Calculate the next timeout for select() */ \
413 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec; \
414 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
415 if (queue->timeout.tv_sec < 0) \
416 queue->timeout.tv_sec = 0; \
418 /* We wouldn't want to go under zero, check for it. */ \
419 if (queue->timeout.tv_usec < 0) { \
420 queue->timeout.tv_sec -= 1; \
421 if (queue->timeout.tv_sec < 0) \
422 queue->timeout.tv_sec = 0; \
423 queue->timeout.tv_usec += 1000000L; \
426 /* We've got the timeout value */ \
429 /* Task is not valid, remove it and try next one. */ \
430 silc_task_remove(queue, task); \
431 task = queue->task; \
432 if (queue->task == NULL) \
436 /* Save the timeout */ \
438 schedule->timeout = &queue->timeout; \
442 /* Execute generic tasks. These are executed only and only if for the
443 specific fd there wasn't other non-timeout tasks. This checks the earlier
444 set fd list, thus the generic tasks apply to all specified fd's. All the
445 generic tasks are executed at once. */
447 #define SILC_SCHEDULE_RUN_GENERIC_TASKS \
449 if (is_run == FALSE) { \
450 SILC_LOG_DEBUG(("Running generic tasks")); \
451 for (i = 0; i <= schedule->fd_list.last_fd; i++) \
452 if (schedule->fd_list.fd[i] != -1) { \
454 /* Check whether this fd is select()ed. */ \
455 if ((FD_ISSET(i, &schedule->in)) || (FD_ISSET(i, &schedule->out))) { \
457 /* It was selected. Now find the tasks from task queue and execute \
458 all generic tasks. */ \
459 if (schedule->generic_queue && schedule->generic_queue->valid) { \
460 queue = schedule->generic_queue; \
465 task = queue->task; \
468 /* Validity of the task is checked always before and after \
469 execution beacuse the task might have been unregistered \
470 in the callback function, ie. it is not valid anymore. */ \
472 if (task->valid && schedule->fd_list.fd[i] != -1) { \
473 /* Task ready for reading */ \
474 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ))) \
475 task->callback(queue, SILC_TASK_READ, \
479 if (task->valid && schedule->fd_list.fd[i] != -1) { \
480 /* Task ready for writing */ \
481 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
482 task->callback(queue, SILC_TASK_WRITE, \
486 if (!task->valid) { \
487 /* Invalid (unregistered) tasks are removed from the \
489 if (queue->task == task->next) { \
490 silc_task_remove(queue, task); \
495 silc_task_remove(queue, task->prev); \
499 /* Break if there isn't more tasks in the queue */ \
500 if (queue->task == task->next) \
511 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
513 struct timeval timeout;
517 struct timeval curtime;
519 SILC_LOG_DEBUG(("In scheduler loop"));
521 /* If the task queues aren't initialized or we aren't valid anymore
523 if ((!schedule->fd_queue && !schedule->timeout_queue
524 && !schedule->generic_queue) || schedule->valid == FALSE) {
525 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
529 /* Clear everything */
530 FD_ZERO(&schedule->in);
531 FD_ZERO(&schedule->out);
532 schedule->max_fd = -1;
535 /* Calculate next timeout for select(). This is the timeout value
536 when at earliest some of the timeout tasks expire. */
537 SILC_SCHEDULE_SELECT_TIMEOUT;
539 /* Add the file descriptors to the fd sets. These are the non-timeout
540 tasks. The select() listens to these file descriptors. */
541 SILC_SCHEDULE_SELECT_TASKS;
543 if (schedule->max_fd == -1 && !schedule->timeout)
546 if (schedule->timeout) {
547 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
548 schedule->timeout->tv_usec));
551 if (timeout_usecs >= 0) {
553 timeout.tv_usec = timeout_usecs;
554 schedule->timeout = &timeout;
557 /* This is the main select(). The program blocks here until some
558 of the selected file descriptors change status or the selected
560 SILC_LOG_DEBUG(("Select"));
561 switch (select(schedule->max_fd + 1, &schedule->in,
562 &schedule->out, 0, schedule->timeout)) {
567 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
571 SILC_LOG_DEBUG(("Running timeout tasks"));
572 gettimeofday(&curtime, NULL);
573 SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
576 /* There is some data available now */
577 SILC_LOG_DEBUG(("Running non-timeout tasks"));
578 SILC_SCHEDULE_RUN_TASKS;
580 SILC_SCHEDULE_RUN_GENERIC_TASKS;
587 /* The SILC scheduler. This is actually the main routine in SILC programs.
588 When this returns the program is to be ended. Before this function can
589 be called, one must call silc_schedule_init function. */
591 void silc_schedule(SilcSchedule schedule)
593 SILC_LOG_DEBUG(("Running scheduler"));
595 if (schedule->valid == FALSE) {
596 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
600 /* Start the scheduler loop */
601 while (silc_schedule_one(schedule, -1))