5 Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
7 Copyright (C) 1998 - 2000 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 /* The actual schedule object. */
25 static SilcSchedule schedule;
27 /* Initializes the schedule. Sets the non-timeout task queue hook and
28 the timeout task queue hook. This must be called before the schedule
31 void silc_schedule_init(SilcTaskQueue *fd_queue,
32 SilcTaskQueue *timeout_queue,
33 SilcTaskQueue *generic_queue,
38 SILC_LOG_DEBUG(("Initializing scheduler"));
40 /* Register the task queues if they are not registered already. In SILC
41 we have by default three task queues. One task queue for non-timeout
42 tasks which perform different kind of I/O on file descriptors, timeout
43 task queue for timeout tasks, and, generic non-timeout task queue whose
44 tasks apply to all connections. */
46 silc_task_queue_alloc(fd_queue, TRUE);
48 silc_task_queue_alloc(timeout_queue, TRUE);
50 silc_task_queue_alloc(generic_queue, TRUE);
52 /* Initialize the schedule */
53 memset(&schedule, 0, sizeof(schedule));
54 schedule.fd_queue = *fd_queue;
55 schedule.timeout_queue = *timeout_queue;
56 schedule.generic_queue = *generic_queue;
57 schedule.fd_list.fd = silc_calloc(max_fd, sizeof(int));
58 schedule.fd_list.last_fd = 0;
59 schedule.fd_list.max_fd = max_fd;
60 schedule.timeout = NULL;
61 schedule.valid = TRUE;
62 FD_ZERO(&schedule.in);
63 FD_ZERO(&schedule.out);
65 for (i = 0; i < max_fd; i++)
66 schedule.fd_list.fd[i] = -1;
69 /* Uninitializes the schedule. This is called when the program is ready
70 to end. This removes all tasks and task queues. */
72 int silc_schedule_uninit()
75 SILC_LOG_DEBUG(("Uninitializing scheduler"));
77 if (schedule.valid == TRUE)
80 /* Unregister all tasks */
81 if (schedule.fd_queue)
82 silc_task_remove(schedule.fd_queue, SILC_ALL_TASKS);
83 if (schedule.timeout_queue)
84 silc_task_remove(schedule.timeout_queue, SILC_ALL_TASKS);
85 if (schedule.generic_queue)
86 silc_task_remove(schedule.generic_queue, SILC_ALL_TASKS);
88 /* Unregister all task queues */
89 if (schedule.fd_queue)
90 silc_task_queue_free(schedule.fd_queue);
91 if (schedule.timeout_queue)
92 silc_task_queue_free(schedule.timeout_queue);
93 if (schedule.generic_queue)
94 silc_task_queue_free(schedule.generic_queue);
96 /* Clear the fd list */
97 if (schedule.fd_list.fd) {
98 memset(schedule.fd_list.fd, -1, schedule.fd_list.max_fd);
99 silc_free(schedule.fd_list.fd);
102 memset(&schedule, 'F', sizeof(schedule));
106 /* Stops the schedule even if it is not supposed to be stopped yet.
107 After calling this, one should call silc_schedule_uninit (after the
108 silc_schedule has returned). */
110 void silc_schedule_stop()
112 SILC_LOG_DEBUG(("Stopping scheduler"));
114 if (schedule.valid == TRUE)
115 schedule.valid = FALSE;
118 /* Sets a file descriptor to be listened by select() in scheduler. One can
119 call this directly if wanted. This can be called multiple times for
120 one file descriptor to set different iomasks. */
122 void silc_schedule_set_listen_fd(int fd, uint32 iomask)
124 assert(schedule.valid != FALSE);
125 assert(fd < schedule.fd_list.max_fd);
127 schedule.fd_list.fd[fd] = iomask;
129 if (fd > schedule.fd_list.last_fd)
130 schedule.fd_list.last_fd = fd;
133 /* Removes a file descriptor from listen list. */
135 void silc_schedule_unset_listen_fd(int fd)
137 assert(schedule.valid != FALSE);
138 assert(fd < schedule.fd_list.max_fd);
140 schedule.fd_list.fd[fd] = -1;
142 if (fd == schedule.fd_list.last_fd) {
145 for (i = fd; i >= 0; i--)
146 if (schedule.fd_list.fd[i] != -1)
149 schedule.fd_list.last_fd = i < 0 ? 0 : i;
153 /* Executes tasks matching the file descriptor set by select(). The task
154 remains on the task queue after execution. Invalid tasks are removed
155 here from the task queue. This macro is used by silc_schedule function.
156 We don't have to care about the tasks priority here because the tasks
157 are sorted in their priority order already at the registration phase. */
159 #define SILC_SCHEDULE_RUN_TASKS \
161 queue = schedule.fd_queue; \
162 if (queue && queue->valid == TRUE && queue->task) { \
163 task = queue->task; \
165 /* Walk thorugh all tasks in the particular task queue and \
166 execute the callback functions of those tasks matching the \
167 fd set by select(). */ \
169 /* Validity of the task is checked always before and after \
170 execution beacuse the task might have been unregistered \
171 in the callback function, ie. it is not valid anymore. */ \
174 /* Task ready for reading */ \
175 if ((FD_ISSET(task->fd, &schedule.in)) && \
176 (task->iomask & (1L << SILC_TASK_READ))) { \
177 task->callback(queue, SILC_TASK_READ, task->context, task->fd); \
183 /* Task ready for writing */ \
184 if ((FD_ISSET(task->fd, &schedule.out)) && \
185 (task->iomask & (1L << SILC_TASK_WRITE))) { \
186 task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
191 if (!task->valid) { \
192 /* Invalid (unregistered) tasks are removed from the \
194 if (queue->task == task->next) { \
195 silc_task_remove(queue, task); \
200 silc_task_remove(queue, task->prev); \
204 /* Break if there isn't more tasks in the queue */ \
205 if (queue->task == task->next) \
213 /* Selects tasks to be listened by select(). These are the non-timeout
214 tasks. This checks the scheduler's fd list. This macro is used by
215 silc_schedule function. */
217 #define SILC_SCHEDULE_SELECT_TASKS \
219 for (i = 0; i <= schedule.fd_list.last_fd; i++) { \
220 if (schedule.fd_list.fd[i] != -1) { \
222 /* Set the max fd value for select() to listen */ \
223 if (i > schedule.max_fd) \
224 schedule.max_fd = i; \
226 /* Add tasks for reading */ \
227 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ))) \
228 FD_SET(i, &schedule.in); \
230 /* Add tasks for writing */ \
231 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
232 FD_SET(i, &schedule.out); \
237 /* Executes all tasks whose timeout has expired. The task is removed from
238 the task queue after the callback function has returned. Also, invalid
239 tasks are removed here. The current time must be get before calling this
240 macro. This macro is used by silc_schedule function. We don't have to
241 care about priorities because tasks are already sorted in their priority
242 order at the registration phase. */
244 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS \
246 queue = schedule.timeout_queue; \
247 if (queue && queue->valid == TRUE && queue->task) { \
248 task = queue->task; \
250 /* Walk thorugh all tasks in the particular task queue \
251 and run all the expired tasks. */ \
253 /* Execute the task if the timeout has expired */ \
254 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
256 /* Task ready for reading */ \
258 if ((task->iomask & (1L << SILC_TASK_READ))) \
259 task->callback(queue, SILC_TASK_READ, \
260 task->context, task->fd); \
263 /* Task ready for writing */ \
265 if ((task->iomask & (1L << SILC_TASK_WRITE))) \
266 task->callback(queue, SILC_TASK_WRITE, \
267 task->context, task->fd); \
270 /* Break if there isn't more tasks in the queue */ \
271 if (queue->task == task->next) { \
272 /* Remove the task from queue */ \
273 silc_task_remove(queue, task); \
279 /* Remove the task from queue */ \
280 silc_task_remove(queue, task->prev); \
282 /* The timeout hasn't expired, check for next one */ \
284 /* Break if there isn't more tasks in the queue */ \
285 if (queue->task == task->next) \
294 /* Calculates next timeout for select(). This is the timeout value
295 when at earliest some of the timeout tasks expire. If this is in the
296 past, they will be run now. This macro is used by the silc_schedule
299 #define SILC_SCHEDULE_SELECT_TIMEOUT \
301 if (schedule.timeout_queue && schedule.timeout_queue->valid == TRUE) { \
302 queue = schedule.timeout_queue; \
305 /* Get the current time */ \
306 gettimeofday(&curtime, NULL); \
307 schedule.timeout = NULL; \
309 /* First task in the task queue has always the smallest timeout. */ \
310 task = queue->task; \
312 if (task && task->valid == TRUE) { \
314 /* If the timeout is in past, we will run the task and all other \
315 timeout tasks from the past. */ \
316 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
317 SILC_SCHEDULE_RUN_TIMEOUT_TASKS; \
319 /* The task(s) has expired and doesn't exist on the task queue \
320 anymore. We continue with new timeout. */ \
321 queue = schedule.timeout_queue; \
322 task = queue->task; \
323 if (task == NULL || task->valid == FALSE) \
328 /* Calculate the next timeout for select() */ \
329 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec; \
330 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
332 /* We wouldn't want to go under zero, check for it. */ \
333 if (queue->timeout.tv_usec < 0) { \
334 queue->timeout.tv_sec -= 1; \
335 queue->timeout.tv_usec += 1000000L; \
338 /* We've got the timeout value */ \
341 /* Task is not valid, remove it and try next one. */ \
342 silc_task_remove(queue, task); \
343 task = queue->task; \
344 if (queue->task == NULL) \
348 /* Save the timeout */ \
350 schedule.timeout = &queue->timeout; \
354 /* Execute generic tasks. These are executed only and only if for the
355 specific fd there wasn't other non-timeout tasks. This checks the earlier
356 set fd list, thus the generic tasks apply to all specified fd's. All the
357 generic tasks are executed at once. */
359 #define SILC_SCHEDULE_RUN_GENERIC_TASKS \
361 if (is_run == FALSE) { \
362 SILC_LOG_DEBUG(("Running generic tasks")); \
363 for (i = 0; i <= schedule.fd_list.last_fd; i++) \
364 if (schedule.fd_list.fd[i] != -1) { \
366 /* Check whether this fd is select()ed. */ \
367 if ((FD_ISSET(i, &schedule.in)) || (FD_ISSET(i, &schedule.out))) { \
369 /* It was selected. Now find the tasks from task queue and execute \
370 all generic tasks. */ \
371 if (schedule.generic_queue && schedule.generic_queue->valid) { \
372 queue = schedule.generic_queue; \
377 task = queue->task; \
380 /* Validity of the task is checked always before and after \
381 execution beacuse the task might have been unregistered \
382 in the callback function, ie. it is not valid anymore. */ \
384 if (task->valid && schedule.fd_list.fd[i] != -1) { \
385 /* Task ready for reading */ \
386 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ))) \
387 task->callback(queue, SILC_TASK_READ, \
391 if (task->valid && schedule.fd_list.fd[i] != -1) { \
392 /* Task ready for writing */ \
393 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
394 task->callback(queue, SILC_TASK_WRITE, \
398 if (!task->valid) { \
399 /* Invalid (unregistered) tasks are removed from the \
401 if (queue->task == task->next) { \
402 silc_task_remove(queue, task); \
407 silc_task_remove(queue, task->prev); \
411 /* Break if there isn't more tasks in the queue */ \
412 if (queue->task == task->next) \
423 int silc_schedule_one(int timeout_usecs)
425 struct timeval timeout;
429 struct timeval curtime;
431 SILC_LOG_DEBUG(("In scheduler loop"));
433 /* If the task queues aren't initialized or we aren't valid anymore
435 if ((!schedule.fd_queue && !schedule.timeout_queue
436 && !schedule.generic_queue) || schedule.valid == FALSE) {
437 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
441 /* Clear everything */
442 FD_ZERO(&schedule.in);
443 FD_ZERO(&schedule.out);
444 schedule.max_fd = -1;
447 /* Calculate next timeout for select(). This is the timeout value
448 when at earliest some of the timeout tasks expire. */
449 SILC_SCHEDULE_SELECT_TIMEOUT;
451 /* Add the file descriptors to the fd sets. These are the non-timeout
452 tasks. The select() listens to these file descriptors. */
453 SILC_SCHEDULE_SELECT_TASKS;
455 if (schedule.max_fd == -1 && !schedule.timeout)
458 if (schedule.timeout) {
459 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule.timeout->tv_sec,
460 schedule.timeout->tv_usec));
463 if (timeout_usecs >= 0) {
465 timeout.tv_usec = timeout_usecs;
466 schedule.timeout = &timeout;
469 /* This is the main select(). The program blocks here until some
470 of the selected file descriptors change status or the selected
472 SILC_LOG_DEBUG(("Select"));
473 switch (select(schedule.max_fd + 1, &schedule.in,
474 &schedule.out, 0, schedule.timeout)) {
480 if (errno == EINVAL && schedule.timeout) {
481 SILC_LOG_ERROR(("Invalid argument (invalid timeout): %lu %lu",
482 schedule.timeout->tv_sec, schedule.timeout->tv_usec));
485 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
489 SILC_LOG_DEBUG(("Running timeout tasks"));
490 gettimeofday(&curtime, NULL);
491 SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
494 /* There is some data available now */
495 SILC_LOG_DEBUG(("Running non-timeout tasks"));
496 SILC_SCHEDULE_RUN_TASKS;
498 SILC_SCHEDULE_RUN_GENERIC_TASKS;
504 /* The SILC scheduler. This is actually the main routine in SILC programs.
505 When this returns the program is to be ended. Before this function can
506 be called, one must call silc_schedule_init function. */
510 SILC_LOG_DEBUG(("Running scheduler"));
512 if (schedule.valid == FALSE) {
513 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
517 /* Start the scheduler loop */
518 while (silc_schedule_one(-1))