removed a function
[silc.git] / lib / silcutil / silcschedule.c
1 /*
2
3   silcschedule.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 1998 - 2000 Pekka Riikonen
8
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.
13   
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.
18
19 */
20 /* $Id$ */
21
22 #include "silcincludes.h"
23
24 /* The actual schedule object. */
25 static SilcSchedule schedule;
26
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
29    is able to work. */
30
31 void silc_schedule_init(SilcTaskQueue *fd_queue,
32                         SilcTaskQueue *timeout_queue,
33                         SilcTaskQueue *generic_queue,
34                         int max_fd)
35 {
36   int i;
37
38   SILC_LOG_DEBUG(("Initializing scheduler"));
39
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. */
45   if (!*fd_queue)
46     silc_task_queue_alloc(fd_queue, TRUE);
47   if (!*timeout_queue)
48     silc_task_queue_alloc(timeout_queue, TRUE);
49   if (!*generic_queue)
50     silc_task_queue_alloc(generic_queue, TRUE);
51
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);
64   schedule.max_fd = -1;
65   for (i = 0; i < max_fd; i++)
66     schedule.fd_list.fd[i] = -1;
67 }
68
69 /* Uninitializes the schedule. This is called when the program is ready
70    to end. This removes all tasks and task queues. */
71
72 int silc_schedule_uninit()
73 {
74
75   SILC_LOG_DEBUG(("Uninitializing scheduler"));
76
77   if (schedule.valid == TRUE)
78     return FALSE;
79
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);
87
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);
95
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);
100   }
101
102   memset(&schedule, 'F', sizeof(schedule));
103   return TRUE;
104 }
105
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). */
109
110 void silc_schedule_stop()
111 {
112   SILC_LOG_DEBUG(("Stopping scheduler"));
113
114   if (schedule.valid == TRUE)
115     schedule.valid = FALSE;
116 }
117
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. */
121
122 void silc_schedule_set_listen_fd(int fd, unsigned int iomask)
123 {
124   assert(schedule.valid != FALSE);
125   assert(fd < schedule.fd_list.max_fd);
126
127   schedule.fd_list.fd[fd] = iomask;
128   
129   if (fd > schedule.fd_list.last_fd)
130     schedule.fd_list.last_fd = fd;
131 }
132
133 /* Removes a file descriptor from listen list. */
134
135 void silc_schedule_unset_listen_fd(int fd)
136 {
137   assert(schedule.valid != FALSE);
138   assert(fd < schedule.fd_list.max_fd);
139
140   schedule.fd_list.fd[fd] = -1;
141   
142   if (fd == schedule.fd_list.last_fd) {
143     int i;
144
145     for (i = fd; i >= 0; i--)
146       if (schedule.fd_list.fd[i] != -1)
147         break;
148
149     schedule.fd_list.last_fd = i < 0 ? 0 : i;
150   }
151 }
152
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. */
158
159 #define SILC_SCHEDULE_RUN_TASKS                                            \
160 do {                                                                       \
161   queue = schedule.fd_queue;                                               \
162   if (queue && queue->valid == TRUE && queue->task) {                      \
163     task = queue->task;                                                    \
164                                                                            \
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(). */                                              \
168     while(1) {                                                             \
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. */         \
172                                                                            \
173       if (task->valid) {                                                   \
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);  \
178           is_run = TRUE;                                                   \
179         }                                                                  \
180       }                                                                    \
181                                                                            \
182       if (task->valid) {                                                   \
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); \
187           is_run = TRUE;                                                   \
188         }                                                                  \
189       }                                                                    \
190                                                                            \
191       if (!task->valid) {                                                  \
192         /* Invalid (unregistered) tasks are removed from the               \
193            task queue. */                                                  \
194         if (queue->task == task->next) {                                   \
195           silc_task_remove(queue, task);                                   \
196           break;                                                           \
197         }                                                                  \
198                                                                            \
199         task = task->next;                                                 \
200         silc_task_remove(queue, task->prev);                               \
201         continue;                                                          \
202       }                                                                    \
203                                                                            \
204       /* Break if there isn't more tasks in the queue */                   \
205       if (queue->task == task->next)                                       \
206         break;                                                             \
207                                                                            \
208       task = task->next;                                                   \
209     }                                                                      \
210   }                                                                        \
211 } while(0)
212
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. */
216
217 #define SILC_SCHEDULE_SELECT_TASKS                              \
218 do {                                                            \
219   for (i = 0; i <= schedule.fd_list.last_fd; i++) {             \
220     if (schedule.fd_list.fd[i] != -1) {                         \
221                                                                 \
222       /* Set the max fd value for select() to listen */         \
223       if (i > schedule.max_fd)                                  \
224         schedule.max_fd = i;                                    \
225                                                                 \
226       /* Add tasks for reading */                               \
227       if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ)))    \
228         FD_SET(i, &schedule.in);                                \
229                                                                 \
230       /* Add tasks for writing */                               \
231       if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE)))   \
232         FD_SET(i, &schedule.out);                               \
233     }                                                           \
234   }                                                             \
235 } while(0)
236
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. */
243
244 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS                                 \
245 do {                                                                    \
246   queue = schedule.timeout_queue;                                       \
247   if (queue && queue->valid == TRUE && queue->task) {                   \
248     task = queue->task;                                                 \
249                                                                         \
250     /* Walk thorugh all tasks in the particular task queue              \
251        and run all the expired tasks. */                                \
252     while(1) {                                                          \
253       /* Execute the task if the timeout has expired */                 \
254       if (silc_task_timeout_compare(&task->timeout, &curtime)) {        \
255                                                                         \
256         /* Task ready for reading */                                    \
257         if (task->valid) {                                              \
258           if ((task->iomask & (1L << SILC_TASK_READ)))                  \
259             task->callback(queue, SILC_TASK_READ,                       \
260                            task->context, task->fd);                    \
261         }                                                               \
262                                                                         \
263         /* Task ready for writing */                                    \
264         if (task->valid) {                                              \
265           if ((task->iomask & (1L << SILC_TASK_WRITE)))                 \
266             task->callback(queue, SILC_TASK_WRITE,                      \
267                            task->context, task->fd);                    \
268         }                                                               \
269                                                                         \
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);                                \
274           break;                                                        \
275         }                                                               \
276                                                                         \
277         task = task->next;                                              \
278                                                                         \
279         /* Remove the task from queue */                                \
280         silc_task_remove(queue, task->prev);                            \
281       } else {                                                          \
282         /* The timeout hasn't expired, check for next one */            \
283                                                                         \
284         /* Break if there isn't more tasks in the queue */              \
285         if (queue->task == task->next)                                  \
286           break;                                                        \
287                                                                         \
288         task = task->next;                                              \
289       }                                                                 \
290     }                                                                   \
291   }                                                                     \
292 } while(0)
293
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
297    function. */
298
299 #define SILC_SCHEDULE_SELECT_TIMEOUT                                        \
300 do {                                                                        \
301   if (schedule.timeout_queue && schedule.timeout_queue->valid == TRUE) {    \
302     queue = schedule.timeout_queue;                                         \
303     task = NULL;                                                            \
304                                                                             \
305     /* Get the current time */                                              \
306     gettimeofday(&curtime, NULL);                                           \
307     schedule.timeout = NULL;                                                \
308                                                                             \
309     /* First task in the task queue has always the smallest timeout. */     \
310     task = queue->task;                                                     \
311     while(1) {                                                              \
312       if (task && task->valid == TRUE) {                                    \
313                                                                             \
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;                                  \
318                                                                             \
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)                         \
324             break;                                                          \
325           goto cont;                                                        \
326         } else {                                                            \
327  cont:                                                                      \
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; \
331                                                                             \
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;                             \
336           }                                                                 \
337         }                                                                   \
338         /* We've got the timeout value */                                   \
339         break;                                                              \
340       } else {                                                              \
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)                                            \
345           break;                                                            \
346       }                                                                     \
347     }                                                                       \
348     /* Save the timeout */                                                  \
349     if (task)                                                               \
350       schedule.timeout = &queue->timeout;                                   \
351   }                                                                         \
352 } while(0)
353
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. */
358
359 #define SILC_SCHEDULE_RUN_GENERIC_TASKS                                      \
360 do {                                                                         \
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) {                                    \
365                                                                              \
366         /* Check whether this fd is select()ed. */                           \
367         if ((FD_ISSET(i, &schedule.in)) || (FD_ISSET(i, &schedule.out))) {   \
368                                                                              \
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;                                  \
373                                                                              \
374             if (!queue->task)                                                \
375               break;                                                         \
376                                                                              \
377             task = queue->task;                                              \
378                                                                              \
379             while(1) {                                                       \
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. */   \
383                                                                              \
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,                      \
388                                  task->context, i);                          \
389               }                                                              \
390                                                                              \
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,                     \
395                                  task->context, i);                          \
396               }                                                              \
397                                                                              \
398               if (!task->valid) {                                            \
399                 /* Invalid (unregistered) tasks are removed from the         \
400                    task queue. */                                            \
401                 if (queue->task == task->next) {                             \
402                   silc_task_remove(queue, task);                             \
403                   break;                                                     \
404                 }                                                            \
405                                                                              \
406                 task = task->next;                                           \
407                 silc_task_remove(queue, task->prev);                         \
408                 continue;                                                    \
409               }                                                              \
410                                                                              \
411               /* Break if there isn't more tasks in the queue */             \
412               if (queue->task == task->next)                                 \
413                 break;                                                       \
414                                                                              \
415               task = task->next;                                             \
416             }                                                                \
417           }                                                                  \
418         }                                                                    \
419       }                                                                      \
420   }                                                                          \
421 } while(0)
422
423 int silc_schedule_one(int timeout_usecs)
424 {
425   struct timeval timeout;
426   int is_run, i;
427   SilcTask task;
428   SilcTaskQueue queue;
429   struct timeval curtime;
430
431   SILC_LOG_DEBUG(("In scheduler loop"));
432
433   /* If the task queues aren't initialized or we aren't valid anymore
434      we will return */
435   if ((!schedule.fd_queue && !schedule.timeout_queue 
436        && !schedule.generic_queue) || schedule.valid == FALSE) {
437     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
438     return FALSE;
439   }
440
441   /* Clear everything */
442   FD_ZERO(&schedule.in);
443   FD_ZERO(&schedule.out);
444   schedule.max_fd = -1;
445   is_run = FALSE;
446
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;
450
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;
454
455   if (schedule.max_fd == -1) {
456     /*SILC_LOG_ERROR(("Nothing to listen, exiting"));*/
457     return FALSE;
458   }
459
460   if (schedule.timeout) {
461     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule.timeout->tv_sec,
462                     schedule.timeout->tv_usec));
463   }
464
465   if (timeout_usecs >= 0) {
466     timeout.tv_sec = 0;
467     timeout.tv_usec = timeout_usecs;
468     schedule.timeout = &timeout;
469   }
470
471   /* This is the main select(). The program blocks here until some
472      of the selected file descriptors change status or the selected
473      timeout expires. */
474   SILC_LOG_DEBUG(("Select"));
475   switch (select(schedule.max_fd + 1, &schedule.in,
476                  &schedule.out, 0, schedule.timeout)) {
477   case -1:
478     /* Error */
479     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
480     break;
481   case 0:
482     /* Timeout */
483     SILC_LOG_DEBUG(("Running timeout tasks"));
484     gettimeofday(&curtime, NULL);
485     SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
486     break;
487   default:
488     /* There is some data available now */
489     SILC_LOG_DEBUG(("Running non-timeout tasks"));
490     SILC_SCHEDULE_RUN_TASKS;
491
492     SILC_SCHEDULE_RUN_GENERIC_TASKS;
493     break;
494   }
495   return TRUE;
496 }
497
498 /* The SILC scheduler. This is actually the main routine in SILC programs.
499    When this returns the program is to be ended. Before this function can
500    be called, one must call silc_schedule_init function. */
501
502 void silc_schedule()
503 {
504   SILC_LOG_DEBUG(("Running scheduler"));
505
506   if (schedule.valid == FALSE) {
507     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
508     return;
509   }
510
511   /* Start the scheduler loop */
512   while (silc_schedule_one(-1)) ;
513 }