Added silc_schedule_one function to run the scheduler only ones.
[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 /*
21  * $Id$
22  * $Log$
23  * Revision 1.2  2000/09/28 08:50:31  priikone
24  *      Added silc_schedule_one function to run the scheduler only ones.
25  *      Patch by cras.
26  *
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.
31  *
32  * Revision 1.3  2000/07/18 06:51:58  priikone
33  *      Debug version bug fixes.
34  *
35  * Revision 1.2  2000/07/05 06:06:35  priikone
36  *      Global cosmetic change.
37  *
38  * Revision 1.1.1.1  2000/06/27 11:36:55  priikone
39  *      Imported from internal CVS/Added Log headers.
40  *
41  *
42  */
43
44 #include "silcincludes.h"
45
46 /* The actual schedule object. */
47 static SilcSchedule schedule;
48
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
51    is able to work. */
52
53 void silc_schedule_init(SilcTaskQueue *fd_queue,
54                         SilcTaskQueue *timeout_queue,
55                         SilcTaskQueue *generic_queue,
56                         int max_fd)
57 {
58   int i;
59
60   SILC_LOG_DEBUG(("Initializing scheduler"));
61
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. */
67   if (!*fd_queue)
68     silc_task_queue_alloc(fd_queue, TRUE);
69   if (!*timeout_queue)
70     silc_task_queue_alloc(timeout_queue, TRUE);
71   if (!*generic_queue)
72     silc_task_queue_alloc(generic_queue, TRUE);
73
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);
86   schedule.max_fd = -1;
87   for (i = 0; i < max_fd; i++)
88     schedule.fd_list.fd[i] = -1;
89 }
90
91 /* Uninitializes the schedule. This is called when the program is ready
92    to end. This removes all tasks and task queues. */
93
94 int silc_schedule_uninit()
95 {
96
97   SILC_LOG_DEBUG(("Uninitializing scheduler"));
98
99   if (schedule.valid == TRUE)
100     return FALSE;
101
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);
109
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);
117
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);
122   }
123
124   memset(&schedule, 'F', sizeof(schedule));
125   return TRUE;
126 }
127
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). */
131
132 void silc_schedule_stop()
133 {
134   SILC_LOG_DEBUG(("Stopping scheduler"));
135
136   if (schedule.valid == TRUE)
137     schedule.valid = FALSE;
138 }
139
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. */
143
144 void silc_schedule_set_listen_fd(int fd, unsigned int iomask)
145 {
146   assert(schedule.valid != FALSE);
147   assert(fd < schedule.fd_list.max_fd);
148
149   schedule.fd_list.fd[fd] = iomask;
150   
151   if (fd > schedule.fd_list.last_fd)
152     schedule.fd_list.last_fd = fd;
153 }
154
155 /* Removes a file descriptor from listen list. */
156
157 void silc_schedule_unset_listen_fd(int fd)
158 {
159   assert(schedule.valid != FALSE);
160   assert(fd < schedule.fd_list.max_fd);
161
162   schedule.fd_list.fd[fd] = -1;
163   
164   if (fd == schedule.fd_list.last_fd) {
165     int i;
166
167     for (i = fd; i >= 0; i--)
168       if (schedule.fd_list.fd[i] != -1)
169         break;
170
171     schedule.fd_list.last_fd = i < 0 ? 0 : i;
172   }
173 }
174
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. */
180
181 #define SILC_SCHEDULE_RUN_TASKS                                            \
182 do {                                                                       \
183   queue = schedule.fd_queue;                                               \
184   if (queue && queue->valid == TRUE && queue->task) {                      \
185     task = queue->task;                                                    \
186                                                                            \
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(). */                                              \
190     while(1) {                                                             \
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. */         \
194                                                                            \
195       if (task->valid) {                                                   \
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);  \
200           is_run = TRUE;                                                   \
201         }                                                                  \
202       }                                                                    \
203                                                                            \
204       if (task->valid) {                                                   \
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); \
209           is_run = TRUE;                                                   \
210         }                                                                  \
211       }                                                                    \
212                                                                            \
213       if (!task->valid) {                                                  \
214         /* Invalid (unregistered) tasks are removed from the               \
215            task queue. */                                                  \
216         if (queue->task == task->next) {                                   \
217           silc_task_remove(queue, task);                                   \
218           break;                                                           \
219         }                                                                  \
220                                                                            \
221         task = task->next;                                                 \
222         silc_task_remove(queue, task->prev);                               \
223         continue;                                                          \
224       }                                                                    \
225                                                                            \
226       /* Break if there isn't more tasks in the queue */                   \
227       if (queue->task == task->next)                                       \
228         break;                                                             \
229                                                                            \
230       task = task->next;                                                   \
231     }                                                                      \
232   }                                                                        \
233 } while(0)
234
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. */
238
239 #define SILC_SCHEDULE_SELECT_TASKS                              \
240 do {                                                            \
241   for (i = 0; i <= schedule.fd_list.last_fd; i++) {             \
242     if (schedule.fd_list.fd[i] != -1) {                         \
243                                                                 \
244       /* Set the max fd value for select() to listen */         \
245       if (i > schedule.max_fd)                                  \
246         schedule.max_fd = i;                                    \
247                                                                 \
248       /* Add tasks for reading */                               \
249       if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ)))    \
250         FD_SET(i, &schedule.in);                                \
251                                                                 \
252       /* Add tasks for writing */                               \
253       if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE)))   \
254         FD_SET(i, &schedule.out);                               \
255     }                                                           \
256   }                                                             \
257 } while(0)
258
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. */
265
266 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS                                 \
267 do {                                                                    \
268   queue = schedule.timeout_queue;                                       \
269   if (queue && queue->valid == TRUE && queue->task) {                   \
270     task = queue->task;                                                 \
271                                                                         \
272     /* Walk thorugh all tasks in the particular task queue              \
273        and run all the expired tasks. */                                \
274     while(1) {                                                          \
275       /* Execute the task if the timeout has expired */                 \
276       if (silc_task_timeout_compare(&task->timeout, &curtime)) {        \
277                                                                         \
278         /* Task ready for reading */                                    \
279         if (task->valid) {                                              \
280           if ((task->iomask & (1L << SILC_TASK_READ)))                  \
281             task->callback(queue, SILC_TASK_READ,                       \
282                            task->context, task->fd);                    \
283         }                                                               \
284                                                                         \
285         /* Task ready for writing */                                    \
286         if (task->valid) {                                              \
287           if ((task->iomask & (1L << SILC_TASK_WRITE)))                 \
288             task->callback(queue, SILC_TASK_WRITE,                      \
289                            task->context, task->fd);                    \
290         }                                                               \
291                                                                         \
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);                                \
296           break;                                                        \
297         }                                                               \
298                                                                         \
299         task = task->next;                                              \
300                                                                         \
301         /* Remove the task from queue */                                \
302         silc_task_remove(queue, task->prev);                            \
303       } else {                                                          \
304         /* The timeout hasn't expired, check for next one */            \
305                                                                         \
306         /* Break if there isn't more tasks in the queue */              \
307         if (queue->task == task->next)                                  \
308           break;                                                        \
309                                                                         \
310         task = task->next;                                              \
311       }                                                                 \
312     }                                                                   \
313   }                                                                     \
314 } while(0)
315
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
319    function. */
320
321 #define SILC_SCHEDULE_SELECT_TIMEOUT                                        \
322 do {                                                                        \
323   if (schedule.timeout_queue && schedule.timeout_queue->valid == TRUE) {    \
324     queue = schedule.timeout_queue;                                         \
325     task = NULL;                                                            \
326                                                                             \
327     /* Get the current time */                                              \
328     gettimeofday(&curtime, NULL);                                           \
329     schedule.timeout = NULL;                                                \
330                                                                             \
331     /* First task in the task queue has always the smallest timeout. */     \
332     task = queue->task;                                                     \
333     while(1) {                                                              \
334       if (task && task->valid == TRUE) {                                    \
335                                                                             \
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;                                  \
340                                                                             \
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)                         \
346             break;                                                          \
347           goto cont;                                                        \
348         } else {                                                            \
349  cont:                                                                      \
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; \
353                                                                             \
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;                             \
358           }                                                                 \
359         }                                                                   \
360         /* We've got the timeout value */                                   \
361         break;                                                              \
362       } else {                                                              \
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)                                            \
367           break;                                                            \
368       }                                                                     \
369     }                                                                       \
370     /* Save the timeout */                                                  \
371     if (task)                                                               \
372       schedule.timeout = &queue->timeout;                                   \
373   }                                                                         \
374 } while(0)
375
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. */
380
381 #define SILC_SCHEDULE_RUN_GENERIC_TASKS                                      \
382 do {                                                                         \
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) {                                    \
387                                                                              \
388         /* Check whether this fd is select()ed. */                           \
389         if ((FD_ISSET(i, &schedule.in)) || (FD_ISSET(i, &schedule.out))) {   \
390                                                                              \
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;                                  \
395                                                                              \
396             if (!queue->task)                                                \
397               break;                                                         \
398                                                                              \
399             task = queue->task;                                              \
400                                                                              \
401             while(1) {                                                       \
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. */   \
405                                                                              \
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,                      \
410                                  task->context, i);                          \
411               }                                                              \
412                                                                              \
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,                     \
417                                  task->context, i);                          \
418               }                                                              \
419                                                                              \
420               if (!task->valid) {                                            \
421                 /* Invalid (unregistered) tasks are removed from the         \
422                    task queue. */                                            \
423                 if (queue->task == task->next) {                             \
424                   silc_task_remove(queue, task);                             \
425                   break;                                                     \
426                 }                                                            \
427                                                                              \
428                 task = task->next;                                           \
429                 silc_task_remove(queue, task->prev);                         \
430                 continue;                                                    \
431               }                                                              \
432                                                                              \
433               /* Break if there isn't more tasks in the queue */             \
434               if (queue->task == task->next)                                 \
435                 break;                                                       \
436                                                                              \
437               task = task->next;                                             \
438             }                                                                \
439           }                                                                  \
440         }                                                                    \
441       }                                                                      \
442   }                                                                          \
443 } while(0)
444
445 int silc_schedule_one(int timeout_usecs)
446 {
447   struct timeval timeout;
448   int is_run, i;
449   SilcTask task;
450   SilcTaskQueue queue;
451   struct timeval curtime;
452
453   SILC_LOG_DEBUG(("In scheduler loop"));
454
455   /* If the task queues aren't initialized or we aren't valid anymore
456      we will return */
457   if ((!schedule.fd_queue && !schedule.timeout_queue 
458        && !schedule.generic_queue) || schedule.valid == FALSE) {
459     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
460     return FALSE;
461   }
462
463   /* Clear everything */
464   FD_ZERO(&schedule.in);
465   FD_ZERO(&schedule.out);
466   schedule.max_fd = -1;
467   is_run = FALSE;
468
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;
472
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;
476
477   if (schedule.max_fd == -1) {
478     /*SILC_LOG_ERROR(("Nothing to listen, exiting"));*/
479     return FALSE;
480   }
481
482   if (schedule.timeout) {
483     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule.timeout->tv_sec,
484                     schedule.timeout->tv_usec));
485   }
486
487   /* This is the main select(). The program blocks here until some
488      of the selected file descriptors change status or the selected
489      timeout expires. */
490   SILC_LOG_DEBUG(("Select"));
491   if (timeout_usecs < 0)
492     memcpy(&timeout, schedule.timeout, sizeof(timeout));
493   else {
494     timeout.tv_sec = 0;
495     timeout.tv_usec = timeout_usecs;
496   }
497   switch (select(schedule.max_fd + 1, &schedule.in,
498                  &schedule.out, 0, &timeout)) {
499   case -1:
500     /* Error */
501     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
502     break;
503   case 0:
504     /* Timeout */
505     SILC_LOG_DEBUG(("Running timeout tasks"));
506     gettimeofday(&curtime, NULL);
507     SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
508     break;
509   default:
510     /* There is some data available now */
511     SILC_LOG_DEBUG(("Running non-timeout tasks"));
512     SILC_SCHEDULE_RUN_TASKS;
513
514     SILC_SCHEDULE_RUN_GENERIC_TASKS;
515     break;
516   }
517   return TRUE;
518 }
519
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. */
523
524 void silc_schedule()
525 {
526   SILC_LOG_DEBUG(("Running scheduler"));
527
528   if (schedule.valid == FALSE) {
529     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
530     return;
531   }
532
533   /* Start the scheduler loop */
534   while (silc_schedule_one(-1)) ;
535 }