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.2 2000/09/28 08:50:31 priikone
24 * Added silc_schedule_one function to run the scheduler only ones.
27 * Revision 1.1 2000/09/13 17:45:16 priikone
28 * Splitted SILC core library. Core library includes now only
29 * SILC protocol specific stuff. New utility library includes the
30 * old stuff from core library that is more generic purpose stuff.
32 * Revision 1.3 2000/07/18 06:51:58 priikone
33 * Debug version bug fixes.
35 * Revision 1.2 2000/07/05 06:06:35 priikone
36 * Global cosmetic change.
38 * Revision 1.1.1.1 2000/06/27 11:36:55 priikone
39 * Imported from internal CVS/Added Log headers.
44 #include "silcincludes.h"
46 /* The actual schedule object. */
47 static SilcSchedule schedule;
49 /* Initializes the schedule. Sets the non-timeout task queue hook and
50 the timeout task queue hook. This must be called before the schedule
53 void silc_schedule_init(SilcTaskQueue *fd_queue,
54 SilcTaskQueue *timeout_queue,
55 SilcTaskQueue *generic_queue,
60 SILC_LOG_DEBUG(("Initializing scheduler"));
62 /* Register the task queues if they are not registered already. In SILC
63 we have by default three task queues. One task queue for non-timeout
64 tasks which perform different kind of I/O on file descriptors, timeout
65 task queue for timeout tasks, and, generic non-timeout task queue whose
66 tasks apply to all connections. */
68 silc_task_queue_alloc(fd_queue, TRUE);
70 silc_task_queue_alloc(timeout_queue, TRUE);
72 silc_task_queue_alloc(generic_queue, TRUE);
74 /* Initialize the schedule */
75 memset(&schedule, 0, sizeof(schedule));
76 schedule.fd_queue = *fd_queue;
77 schedule.timeout_queue = *timeout_queue;
78 schedule.generic_queue = *generic_queue;
79 schedule.fd_list.fd = silc_calloc(max_fd, sizeof(int));
80 schedule.fd_list.last_fd = 0;
81 schedule.fd_list.max_fd = max_fd;
82 schedule.timeout = NULL;
83 schedule.valid = TRUE;
84 FD_ZERO(&schedule.in);
85 FD_ZERO(&schedule.out);
87 for (i = 0; i < max_fd; i++)
88 schedule.fd_list.fd[i] = -1;
91 /* Uninitializes the schedule. This is called when the program is ready
92 to end. This removes all tasks and task queues. */
94 int silc_schedule_uninit()
97 SILC_LOG_DEBUG(("Uninitializing scheduler"));
99 if (schedule.valid == TRUE)
102 /* Unregister all tasks */
103 if (schedule.fd_queue)
104 silc_task_remove(schedule.fd_queue, SILC_ALL_TASKS);
105 if (schedule.timeout_queue)
106 silc_task_remove(schedule.timeout_queue, SILC_ALL_TASKS);
107 if (schedule.generic_queue)
108 silc_task_remove(schedule.generic_queue, SILC_ALL_TASKS);
110 /* Unregister all task queues */
111 if (schedule.fd_queue)
112 silc_task_queue_free(schedule.fd_queue);
113 if (schedule.timeout_queue)
114 silc_task_queue_free(schedule.timeout_queue);
115 if (schedule.generic_queue)
116 silc_task_queue_free(schedule.generic_queue);
118 /* Clear the fd list */
119 if (schedule.fd_list.fd) {
120 memset(schedule.fd_list.fd, -1, schedule.fd_list.max_fd);
121 silc_free(schedule.fd_list.fd);
124 memset(&schedule, 'F', sizeof(schedule));
128 /* Stops the schedule even if it is not supposed to be stopped yet.
129 After calling this, one should call silc_schedule_uninit (after the
130 silc_schedule has returned). */
132 void silc_schedule_stop()
134 SILC_LOG_DEBUG(("Stopping scheduler"));
136 if (schedule.valid == TRUE)
137 schedule.valid = FALSE;
140 /* Sets a file descriptor to be listened by select() in scheduler. One can
141 call this directly if wanted. This can be called multiple times for
142 one file descriptor to set different iomasks. */
144 void silc_schedule_set_listen_fd(int fd, unsigned int iomask)
146 assert(schedule.valid != FALSE);
147 assert(fd < schedule.fd_list.max_fd);
149 schedule.fd_list.fd[fd] = iomask;
151 if (fd > schedule.fd_list.last_fd)
152 schedule.fd_list.last_fd = fd;
155 /* Removes a file descriptor from listen list. */
157 void silc_schedule_unset_listen_fd(int fd)
159 assert(schedule.valid != FALSE);
160 assert(fd < schedule.fd_list.max_fd);
162 schedule.fd_list.fd[fd] = -1;
164 if (fd == schedule.fd_list.last_fd) {
167 for (i = fd; i >= 0; i--)
168 if (schedule.fd_list.fd[i] != -1)
171 schedule.fd_list.last_fd = i < 0 ? 0 : i;
175 /* Executes tasks matching the file descriptor set by select(). The task
176 remains on the task queue after execution. Invalid tasks are removed
177 here from the task queue. This macro is used by silc_schedule function.
178 We don't have to care about the tasks priority here because the tasks
179 are sorted in their priority order already at the registration phase. */
181 #define SILC_SCHEDULE_RUN_TASKS \
183 queue = schedule.fd_queue; \
184 if (queue && queue->valid == TRUE && queue->task) { \
185 task = queue->task; \
187 /* Walk thorugh all tasks in the particular task queue and \
188 execute the callback functions of those tasks matching the \
189 fd set by select(). */ \
191 /* Validity of the task is checked always before and after \
192 execution beacuse the task might have been unregistered \
193 in the callback function, ie. it is not valid anymore. */ \
196 /* Task ready for reading */ \
197 if ((FD_ISSET(task->fd, &schedule.in)) && \
198 (task->iomask & (1L << SILC_TASK_READ))) { \
199 task->callback(queue, SILC_TASK_READ, task->context, task->fd); \
205 /* Task ready for writing */ \
206 if ((FD_ISSET(task->fd, &schedule.out)) && \
207 (task->iomask & (1L << SILC_TASK_WRITE))) { \
208 task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
213 if (!task->valid) { \
214 /* Invalid (unregistered) tasks are removed from the \
216 if (queue->task == task->next) { \
217 silc_task_remove(queue, task); \
222 silc_task_remove(queue, task->prev); \
226 /* Break if there isn't more tasks in the queue */ \
227 if (queue->task == task->next) \
235 /* Selects tasks to be listened by select(). These are the non-timeout
236 tasks. This checks the scheduler's fd list. This macro is used by
237 silc_schedule function. */
239 #define SILC_SCHEDULE_SELECT_TASKS \
241 for (i = 0; i <= schedule.fd_list.last_fd; i++) { \
242 if (schedule.fd_list.fd[i] != -1) { \
244 /* Set the max fd value for select() to listen */ \
245 if (i > schedule.max_fd) \
246 schedule.max_fd = i; \
248 /* Add tasks for reading */ \
249 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ))) \
250 FD_SET(i, &schedule.in); \
252 /* Add tasks for writing */ \
253 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
254 FD_SET(i, &schedule.out); \
259 /* Executes all tasks whose timeout has expired. The task is removed from
260 the task queue after the callback function has returned. Also, invalid
261 tasks are removed here. The current time must be get before calling this
262 macro. This macro is used by silc_schedule function. We don't have to
263 care about priorities because tasks are already sorted in their priority
264 order at the registration phase. */
266 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS \
268 queue = schedule.timeout_queue; \
269 if (queue && queue->valid == TRUE && queue->task) { \
270 task = queue->task; \
272 /* Walk thorugh all tasks in the particular task queue \
273 and run all the expired tasks. */ \
275 /* Execute the task if the timeout has expired */ \
276 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
278 /* Task ready for reading */ \
280 if ((task->iomask & (1L << SILC_TASK_READ))) \
281 task->callback(queue, SILC_TASK_READ, \
282 task->context, task->fd); \
285 /* Task ready for writing */ \
287 if ((task->iomask & (1L << SILC_TASK_WRITE))) \
288 task->callback(queue, SILC_TASK_WRITE, \
289 task->context, task->fd); \
292 /* Break if there isn't more tasks in the queue */ \
293 if (queue->task == task->next) { \
294 /* Remove the task from queue */ \
295 silc_task_remove(queue, task); \
301 /* Remove the task from queue */ \
302 silc_task_remove(queue, task->prev); \
304 /* The timeout hasn't expired, check for next one */ \
306 /* Break if there isn't more tasks in the queue */ \
307 if (queue->task == task->next) \
316 /* Calculates next timeout for select(). This is the timeout value
317 when at earliest some of the timeout tasks expire. If this is in the
318 past, they will be run now. This macro is used by the silc_schedule
321 #define SILC_SCHEDULE_SELECT_TIMEOUT \
323 if (schedule.timeout_queue && schedule.timeout_queue->valid == TRUE) { \
324 queue = schedule.timeout_queue; \
327 /* Get the current time */ \
328 gettimeofday(&curtime, NULL); \
329 schedule.timeout = NULL; \
331 /* First task in the task queue has always the smallest timeout. */ \
332 task = queue->task; \
334 if (task && task->valid == TRUE) { \
336 /* If the timeout is in past, we will run the task and all other \
337 timeout tasks from the past. */ \
338 if (silc_task_timeout_compare(&task->timeout, &curtime)) { \
339 SILC_SCHEDULE_RUN_TIMEOUT_TASKS; \
341 /* The task(s) has expired and doesn't exist on the task queue \
342 anymore. We continue with new timeout. */ \
343 queue = schedule.timeout_queue; \
344 task = queue->task; \
345 if (task == NULL || task->valid == FALSE) \
350 /* Calculate the next timeout for select() */ \
351 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec; \
352 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
354 /* We wouldn't want to go under zero, check for it. */ \
355 if (queue->timeout.tv_usec < 0) { \
356 queue->timeout.tv_sec -= 1; \
357 queue->timeout.tv_usec += 1000000L; \
360 /* We've got the timeout value */ \
363 /* Task is not valid, remove it and try next one. */ \
364 silc_task_remove(queue, task); \
365 task = queue->task; \
366 if (queue->task == NULL) \
370 /* Save the timeout */ \
372 schedule.timeout = &queue->timeout; \
376 /* Execute generic tasks. These are executed only and only if for the
377 specific fd there wasn't other non-timeout tasks. This checks the earlier
378 set fd list, thus the generic tasks apply to all specified fd's. All the
379 generic tasks are executed at once. */
381 #define SILC_SCHEDULE_RUN_GENERIC_TASKS \
383 if (is_run == FALSE) { \
384 SILC_LOG_DEBUG(("Running generic tasks")); \
385 for (i = 0; i <= schedule.fd_list.last_fd; i++) \
386 if (schedule.fd_list.fd[i] != -1) { \
388 /* Check whether this fd is select()ed. */ \
389 if ((FD_ISSET(i, &schedule.in)) || (FD_ISSET(i, &schedule.out))) { \
391 /* It was selected. Now find the tasks from task queue and execute \
392 all generic tasks. */ \
393 if (schedule.generic_queue && schedule.generic_queue->valid) { \
394 queue = schedule.generic_queue; \
399 task = queue->task; \
402 /* Validity of the task is checked always before and after \
403 execution beacuse the task might have been unregistered \
404 in the callback function, ie. it is not valid anymore. */ \
406 if (task->valid && schedule.fd_list.fd[i] != -1) { \
407 /* Task ready for reading */ \
408 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ))) \
409 task->callback(queue, SILC_TASK_READ, \
413 if (task->valid && schedule.fd_list.fd[i] != -1) { \
414 /* Task ready for writing */ \
415 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE))) \
416 task->callback(queue, SILC_TASK_WRITE, \
420 if (!task->valid) { \
421 /* Invalid (unregistered) tasks are removed from the \
423 if (queue->task == task->next) { \
424 silc_task_remove(queue, task); \
429 silc_task_remove(queue, task->prev); \
433 /* Break if there isn't more tasks in the queue */ \
434 if (queue->task == task->next) \
445 int silc_schedule_one(int timeout_usecs)
447 struct timeval timeout;
451 struct timeval curtime;
453 SILC_LOG_DEBUG(("In scheduler loop"));
455 /* If the task queues aren't initialized or we aren't valid anymore
457 if ((!schedule.fd_queue && !schedule.timeout_queue
458 && !schedule.generic_queue) || schedule.valid == FALSE) {
459 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
463 /* Clear everything */
464 FD_ZERO(&schedule.in);
465 FD_ZERO(&schedule.out);
466 schedule.max_fd = -1;
469 /* Calculate next timeout for select(). This is the timeout value
470 when at earliest some of the timeout tasks expire. */
471 SILC_SCHEDULE_SELECT_TIMEOUT;
473 /* Add the file descriptors to the fd sets. These are the non-timeout
474 tasks. The select() listens to these file descriptors. */
475 SILC_SCHEDULE_SELECT_TASKS;
477 if (schedule.max_fd == -1) {
478 /*SILC_LOG_ERROR(("Nothing to listen, exiting"));*/
482 if (schedule.timeout) {
483 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule.timeout->tv_sec,
484 schedule.timeout->tv_usec));
487 /* This is the main select(). The program blocks here until some
488 of the selected file descriptors change status or the selected
490 SILC_LOG_DEBUG(("Select"));
491 if (timeout_usecs < 0)
492 memcpy(&timeout, schedule.timeout, sizeof(timeout));
495 timeout.tv_usec = timeout_usecs;
497 switch (select(schedule.max_fd + 1, &schedule.in,
498 &schedule.out, 0, &timeout)) {
501 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
505 SILC_LOG_DEBUG(("Running timeout tasks"));
506 gettimeofday(&curtime, NULL);
507 SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
510 /* There is some data available now */
511 SILC_LOG_DEBUG(("Running non-timeout tasks"));
512 SILC_SCHEDULE_RUN_TASKS;
514 SILC_SCHEDULE_RUN_GENERIC_TASKS;
520 /* The SILC scheduler. This is actually the main routine in SILC programs.
521 When this returns the program is to be ended. Before this function can
522 be called, one must call silc_schedule_init function. */
526 SILC_LOG_DEBUG(("Running scheduler"));
528 if (schedule.valid == FALSE) {
529 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
533 /* Start the scheduler loop */
534 while (silc_schedule_one(-1)) ;