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.
23 * Revision 1.1 2000/09/13 17:45:16 priikone
24 * Splitted SILC core library. Core library includes now only
25 * SILC protocol specific stuff. New utility library includes the
26 * old stuff from core library that is more generic purpose stuff.
28 * Revision 1.3 2000/07/18 06:51:58 priikone
29 * Debug version bug fixes.
31 * Revision 1.2 2000/07/05 06:06:35 priikone
32 * Global cosmetic change.
34 * Revision 1.1.1.1 2000/06/27 11:36:55 priikone
35 * Imported from internal CVS/Added Log headers.
40 #include "silcincludes.h"
42 /* The actual schedule object. */
43 static SilcSchedule schedule;
45 /* Initializes the schedule. Sets the non-timeout task queue hook and
46 the timeout task queue hook. This must be called before the schedule
49 void silc_schedule_init(SilcTaskQueue *fd_queue,
50 SilcTaskQueue *timeout_queue,
51 SilcTaskQueue *generic_queue,
56 SILC_LOG_DEBUG(("Initializing scheduler"));
58 /* Register the task queues if they are not registered already. In SILC
59 we have by default three task queues. One task queue for non-timeout
60 tasks which perform different kind of I/O on file descriptors, timeout
61 task queue for timeout tasks, and, generic non-timeout task queue whose
62 tasks apply to all connections. */
64 silc_task_queue_alloc(fd_queue, TRUE);
66 silc_task_queue_alloc(timeout_queue, TRUE);
68 silc_task_queue_alloc(generic_queue, TRUE);
70 /* Initialize the schedule */
71 memset(&schedule, 0, sizeof(schedule));
72 schedule.fd_queue = *fd_queue;
73 schedule.timeout_queue = *timeout_queue;
74 schedule.generic_queue = *generic_queue;
75 schedule.fd_list.fd = silc_calloc(max_fd, sizeof(int));
76 schedule.fd_list.last_fd = 0;
77 schedule.fd_list.max_fd = max_fd;
78 schedule.timeout = NULL;
79 schedule.valid = TRUE;
80 FD_ZERO(&schedule.in);
81 FD_ZERO(&schedule.out);
83 for (i = 0; i < max_fd; i++)
84 schedule.fd_list.fd[i] = -1;
87 /* Uninitializes the schedule. This is called when the program is ready
88 to end. This removes all tasks and task queues. */
90 int silc_schedule_uninit()
93 SILC_LOG_DEBUG(("Uninitializing scheduler"));
95 if (schedule.valid == TRUE)
98 /* Unregister all tasks */
99 if (schedule.fd_queue)
100 silc_task_remove(schedule.fd_queue, SILC_ALL_TASKS);
101 if (schedule.timeout_queue)
102 silc_task_remove(schedule.timeout_queue, SILC_ALL_TASKS);
103 if (schedule.generic_queue)
104 silc_task_remove(schedule.generic_queue, SILC_ALL_TASKS);
106 /* Unregister all task queues */
107 if (schedule.fd_queue)
108 silc_task_queue_free(schedule.fd_queue);
109 if (schedule.timeout_queue)
110 silc_task_queue_free(schedule.timeout_queue);
111 if (schedule.generic_queue)
112 silc_task_queue_free(schedule.generic_queue);
114 /* Clear the fd list */
115 if (schedule.fd_list.fd) {
116 memset(schedule.fd_list.fd, -1, schedule.fd_list.max_fd);
117 silc_free(schedule.fd_list.fd);
120 memset(&schedule, 'F', sizeof(schedule));
124 /* Stops the schedule even if it is not supposed to be stopped yet.
125 After calling this, one should call silc_schedule_uninit (after the
126 silc_schedule has returned). */
128 void silc_schedule_stop()
130 SILC_LOG_DEBUG(("Stopping scheduler"));
132 if (schedule.valid == TRUE)
133 schedule.valid = FALSE;
136 /* Sets a file descriptor to be listened by select() in scheduler. One can
137 call this directly if wanted. This can be called multiple times for
138 one file descriptor to set different iomasks. */
140 void silc_schedule_set_listen_fd(int fd, unsigned int iomask)
142 assert(schedule.valid != FALSE);
143 assert(fd < schedule.fd_list.max_fd);
145 schedule.fd_list.fd[fd] = iomask;
147 if (fd > schedule.fd_list.last_fd)
148 schedule.fd_list.last_fd = fd;
151 /* Removes a file descriptor from listen list. */
153 void silc_schedule_unset_listen_fd(int fd)
155 assert(schedule.valid != FALSE);
156 assert(fd < schedule.fd_list.max_fd);
158 schedule.fd_list.fd[fd] = -1;
160 if (fd == schedule.fd_list.last_fd) {
163 for (i = fd; i >= 0; i--)
164 if (schedule.fd_list.fd[i] != -1)
167 schedule.fd_list.last_fd = i;
171 /* Executes tasks matching the file descriptor set by select(). The task
172 remains on the task queue after execution. Invalid tasks are removed
173 here from the task queue. This macro is used by silc_schedule function.
174 We don't have to care about the tasks priority here because the tasks
175 are sorted in their priority order already at the registration phase. */
177 #define SILC_SCHEDULE_RUN_TASKS \
179 queue = schedule.fd_queue; \
180 if (queue && queue->valid == TRUE && queue->task) { \
181 task = queue->task; \
183 /* Walk thorugh all tasks in the particular task queue and \
184 execute the callback functions of those tasks matching the \
185 fd set by select(). */ \
187 /* Validity of the task is checked always before and after \
188 execution beacuse the task might have been unregistered \
189 in the callback function, ie. it is not valid anymore. */ \
192 /* Task ready for reading */ \
193 if ((FD_ISSET(task->fd, &schedule.in)) && \
194 (task->iomask & (1L << SILC_TASK_READ))) { \
195 task->callback(queue, SILC_TASK_READ, task->context, task->fd); \
201 /* Task ready for writing */ \
202 if ((FD_ISSET(task->fd, &schedule.out)) && \
203 (task->iomask & (1L << SILC_TASK_WRITE))) { \
204 task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
209 if (!task->valid) { \
210 /* Invalid (unregistered) tasks are removed from the \
212 if (queue->task == task->next) { \
213 silc_task_remove(queue, task); \
218 silc_task_remove(queue, task->prev); \
222 /* Break if there isn't more tasks in the queue */ \
223 if (queue->task == task->next) \
231 /* Selects tasks to be listened by select(). These are the non-timeout
232 tasks. This checks the scheduler's fd list. This macro is used by
233 silc_schedule function. */
235 #define SILC_SCHEDULE_SELECT_TASKS \
237 for (i = 0; i <= schedule.fd_list.last_fd; i++) { \
238 if (schedule.fd_list.fd[i] != -1) { \
240 /* Set the max fd value for select() to listen */ \
241 if (i > schedule.max_fd) \
242 schedule.max_fd = i; \
244 /* Add tasks for reading */ \
245 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ))) \
246 FD_SET(i, &schedule.in); \
248 /* Add tasks for writing */ \
249 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
250 FD_SET(i, &schedule.out); \
255 /* Executes all tasks whose timeout has expired. The task is removed from
256 the task queue after the callback function has returned. Also, invalid
257 tasks are removed here. The current time must be get before calling this
258 macro. This macro is used by silc_schedule function. We don't have to
259 care about priorities because tasks are already sorted in their priority
260 order at the registration phase. */
262 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS \
264 queue = schedule.timeout_queue; \
265 if (queue && queue->valid == TRUE && queue->task) { \
266 task = queue->task; \
268 /* Walk thorugh all tasks in the particular task queue \
269 and run all the expired tasks. */ \
271 /* Execute the task if the timeout has expired */ \
272 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
274 /* Task ready for reading */ \
276 if ((task->iomask & (1L << SILC_TASK_READ))) \
277 task->callback(queue, SILC_TASK_READ, \
278 task->context, task->fd); \
281 /* Task ready for writing */ \
283 if ((task->iomask & (1L << SILC_TASK_WRITE))) \
284 task->callback(queue, SILC_TASK_WRITE, \
285 task->context, task->fd); \
288 /* Break if there isn't more tasks in the queue */ \
289 if (queue->task == task->next) { \
290 /* Remove the task from queue */ \
291 silc_task_remove(queue, task); \
297 /* Remove the task from queue */ \
298 silc_task_remove(queue, task->prev); \
300 /* The timeout hasn't expired, check for next one */ \
302 /* Break if there isn't more tasks in the queue */ \
303 if (queue->task == task->next) \
312 /* Calculates next timeout for select(). This is the timeout value
313 when at earliest some of the timeout tasks expire. If this is in the
314 past, they will be run now. This macro is used by the silc_schedule
317 #define SILC_SCHEDULE_SELECT_TIMEOUT \
319 if (schedule.timeout_queue && schedule.timeout_queue->valid == TRUE) { \
320 queue = schedule.timeout_queue; \
323 /* Get the current time */ \
324 gettimeofday(&curtime, NULL); \
325 schedule.timeout = NULL; \
327 /* First task in the task queue has always the smallest timeout. */ \
328 task = queue->task; \
330 if (task && task->valid == TRUE) { \
332 /* If the timeout is in past, we will run the task and all other \
333 timeout tasks from the past. */ \
334 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
335 SILC_SCHEDULE_RUN_TIMEOUT_TASKS; \
337 /* The task(s) has expired and doesn't exist on the task queue \
338 anymore. We continue with new timeout. */ \
339 queue = schedule.timeout_queue; \
340 task = queue->task; \
341 if (task == NULL || task->valid == FALSE) \
346 /* Calculate the next timeout for select() */ \
347 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec; \
348 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
350 /* We wouldn't want to go under zero, check for it. */ \
351 if (queue->timeout.tv_usec < 0) { \
352 queue->timeout.tv_sec -= 1; \
353 queue->timeout.tv_usec += 1000000L; \
356 /* We've got the timeout value */ \
359 /* Task is not valid, remove it and try next one. */ \
360 silc_task_remove(queue, task); \
361 task = queue->task; \
362 if (queue->task == NULL) \
366 /* Save the timeout */ \
368 schedule.timeout = &queue->timeout; \
372 /* Execute generic tasks. These are executed only and only if for the
373 specific fd there wasn't other non-timeout tasks. This checks the earlier
374 set fd list, thus the generic tasks apply to all specified fd's. All the
375 generic tasks are executed at once. */
377 #define SILC_SCHEDULE_RUN_GENERIC_TASKS \
379 if (is_run == FALSE) { \
380 SILC_LOG_DEBUG(("Running generic tasks")); \
381 for (i = 0; i <= schedule.fd_list.last_fd; i++) \
382 if (schedule.fd_list.fd[i] != -1) { \
384 /* Check whether this fd is select()ed. */ \
385 if ((FD_ISSET(i, &schedule.in)) || (FD_ISSET(i, &schedule.out))) { \
387 /* It was selected. Now find the tasks from task queue and execute \
388 all generic tasks. */ \
389 if (schedule.generic_queue && schedule.generic_queue->valid) { \
390 queue = schedule.generic_queue; \
395 task = queue->task; \
398 /* Validity of the task is checked always before and after \
399 execution beacuse the task might have been unregistered \
400 in the callback function, ie. it is not valid anymore. */ \
402 if (task->valid && schedule.fd_list.fd[i] != -1) { \
403 /* Task ready for reading */ \
404 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ))) \
405 task->callback(queue, SILC_TASK_READ, \
409 if (task->valid && schedule.fd_list.fd[i] != -1) { \
410 /* Task ready for writing */ \
411 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
412 task->callback(queue, SILC_TASK_WRITE, \
416 if (!task->valid) { \
417 /* Invalid (unregistered) tasks are removed from the \
419 if (queue->task == task->next) { \
420 silc_task_remove(queue, task); \
425 silc_task_remove(queue, task->prev); \
429 /* Break if there isn't more tasks in the queue */ \
430 if (queue->task == task->next) \
441 /* The SILC scheduler. This is actually the main routine in SILC programs.
442 When this returns the program is to be ended. Before this function can
443 be called, one must call silc_schedule_init function. */
450 struct timeval curtime;
452 SILC_LOG_DEBUG(("Running scheduler"));
454 if (schedule.valid == FALSE) {
455 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
459 /* Start the scheduler loop */
462 SILC_LOG_DEBUG(("In scheduler loop"));
464 /* If the task queues aren't initialized or we aren't valid anymore
466 if ((!schedule.fd_queue && !schedule.timeout_queue
467 && !schedule.generic_queue) || schedule.valid == FALSE) {
468 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
472 /* Clear everything */
473 FD_ZERO(&schedule.in);
474 FD_ZERO(&schedule.out);
475 schedule.max_fd = -1;
478 /* Calculate next timeout for select(). This is the timeout value
479 when at earliest some of the timeout tasks expire. */
480 SILC_SCHEDULE_SELECT_TIMEOUT;
482 /* Add the file descriptors to the fd sets. These are the non-timeout
483 tasks. The select() listens to these file descriptors. */
484 SILC_SCHEDULE_SELECT_TASKS;
486 if (schedule.max_fd == -1) {
487 SILC_LOG_ERROR(("Nothing to listen, exiting"));
491 if (schedule.timeout) {
492 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule.timeout->tv_sec,
493 schedule.timeout->tv_usec));
496 /* This is the main select(). The program blocks here until some
497 of the selected file descriptors change status or the selected
499 SILC_LOG_DEBUG(("Select"));
500 switch(select(schedule.max_fd + 1, &schedule.in,
501 &schedule.out, 0, schedule.timeout)) {
504 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
508 SILC_LOG_DEBUG(("Running timeout tasks"));
509 gettimeofday(&curtime, NULL);
510 SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
513 /* There is some data available now */
514 SILC_LOG_DEBUG(("Running non-timeout tasks"));
515 SILC_SCHEDULE_RUN_TASKS;
517 SILC_SCHEDULE_RUN_GENERIC_TASKS;