45ecdbff1251b42a798d878ef43475a24194ce34
[silc.git] / lib / silcutil / silctask.c
1 /*
2
3   silctask.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 /* Allocates a new task queue into the Silc. If 'valid' is TRUE the
25    queue becomes valid task queue. If it is FALSE scheduler will skip
26    the queue. */
27
28 void silc_task_queue_alloc(SilcTaskQueue *new, int valid)
29 {
30
31   SILC_LOG_DEBUG(("Allocating new task queue"));
32
33   *new = silc_calloc(1, sizeof(**new));
34
35   /* Set the pointers */
36   (*new)->valid = valid;
37   (*new)->task = NULL;
38 }
39
40 /* Free's a task queue. */
41
42 void silc_task_queue_free(SilcTaskQueue old)
43 {
44   if (old)
45     silc_free(old);
46 }
47
48 /* Adds a non-timeout task into the task queue. This function is used
49    by silc_task_register function. Returns a pointer to the registered 
50    task. */
51
52 SilcTask silc_task_add(SilcTaskQueue queue, SilcTask new, 
53                        SilcTaskPriority priority)
54 {
55   SilcTask task, next, prev;
56
57   /* Take the first task in the queue */
58   task = queue->task;
59
60   switch(priority) {
61   case SILC_TASK_PRI_LOW:
62     /* Lowest priority. The task is added at the end of the list. */
63     prev = task->prev;
64     new->prev = prev;
65     new->next = task;
66     prev->next = new;
67     task->prev = new;
68     break;
69   case SILC_TASK_PRI_NORMAL:
70     /* Normal priority. The task is added before lower priority tasks
71        but after tasks with higher priority. */
72     prev = task->prev;
73     while(prev != task) {
74       if (prev->priority > SILC_TASK_PRI_LOW)
75         break;
76       prev = prev->prev;
77     }
78     if (prev == task) {
79       /* There are only lower priorities in the list, we will
80          sit before them and become the first task in the queue. */
81       prev = task->prev;
82       new->prev = prev;
83       new->next = task;
84       task->prev = new;
85       prev->next = new;
86
87       /* We are now the first task in queue */
88       queue->task = new;
89     } else {
90       /* Found a spot from the list, add the task to the list. */
91       next = prev->next;
92       new->prev = prev;
93       new->next = next;
94       prev->next = new;
95       next->prev = new;
96     }
97     break;
98   default:
99     silc_free(new);
100     return NULL;
101   }
102
103   return new;
104 }
105
106 /* Return the timeout task with smallest timeout. */
107
108 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
109 {
110   SilcTask prev, task;
111
112   prev = first->prev;
113
114   if (first == prev)
115     return first;
116
117   task = first;
118   while (1) {
119     if (first == prev)
120       break;
121
122     if (silc_task_timeout_compare(&prev->timeout, &task->timeout))
123       task = prev;
124
125     prev = prev->prev;
126   }
127
128   return task;
129 }
130
131 /* Adds a timeout task into the task queue. This function is used by
132    silc_task_register function. Returns a pointer to the registered 
133    task. Timeout tasks are sorted by their timeout value in ascending
134    order. The priority matters if there are more than one task with
135    same timeout. */
136
137 SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask new,
138                                SilcTaskPriority priority)
139 {
140   SilcTask task, prev, next;
141
142   /* Take the first task in the queue */
143   task = queue->task;
144
145   /* Take last task from the list */
146   prev = task->prev;
147     
148   switch(priority) {
149   case SILC_TASK_PRI_LOW:
150     /* Lowest priority. The task is added at the end of the list. */
151     while(prev != task) {
152
153       /* If we have longer timeout than with the task head of us
154          we have found our spot. */
155       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
156         break;
157
158       /* If we are equal size of timeout we will be after it. */
159       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
160         break;
161
162       /* We have shorter timeout, compare to next one. */
163       prev = prev->prev;
164     }
165     /* Found a spot from the list, add the task to the list. */
166     next = prev->next;
167     new->prev = prev;
168     new->next = next;
169     prev->next = new;
170     next->prev = new;
171     
172     if (prev == task) {
173       /* Check if we are going to be the first task in the queue */
174       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
175         break;
176       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
177         break;
178
179       /* We are now the first task in queue */
180       queue->task = new;
181     }
182     break;
183   case SILC_TASK_PRI_NORMAL:
184     /* Normal priority. The task is added before lower priority tasks
185        but after tasks with higher priority. */
186     while(prev != task) {
187
188       /* If we have longer timeout than with the task head of us
189          we have found our spot. */
190       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
191         break;
192
193       /* If we are equal size of timeout, priority kicks in place. */
194       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
195         if (prev->priority >= SILC_TASK_PRI_NORMAL)
196           break;
197
198       /* We have shorter timeout or higher priority, compare to next one. */
199       prev = prev->prev;
200     }
201     /* Found a spot from the list, add the task to the list. */
202     next = prev->next;
203     new->prev = prev;
204     new->next = next;
205     prev->next = new;
206     next->prev = new;
207     
208     if (prev == task) {
209       /* Check if we are going to be the first task in the queue */
210       if (silc_task_timeout_compare(&prev->timeout, &new->timeout))
211         break;
212       if (!silc_task_timeout_compare(&new->timeout, &prev->timeout))
213         if (prev->priority >= SILC_TASK_PRI_NORMAL)
214           break;
215
216       /* We are now the first task in queue */
217       queue->task = new;
218     }
219     break;
220   default:
221     silc_free(new);
222     return NULL;
223   }
224
225   return new;
226 }
227
228 /* Registers a new task to the task queue. Arguments are as follows:
229       
230    SilcTaskQueue queue        Queue where the task is to be registered
231    int fd                     File descriptor
232    SilcTaskCallback cb        Callback function to call
233    void *context              Context to be passed to callback function
234    long seconds               Seconds to timeout
235    long useconds              Microseconds to timeout
236    SilcTaskType type          Type of the task
237    SilcTaskPriority priority  Priority of the task
238    
239    The same function is used to register all types of tasks. The type
240    argument tells what type of the task is. Note that when registering
241    non-timeout tasks one should also pass 0 as timeout as timeout will
242    be ignored anyway. Also, note, that one cannot register timeout task
243    with 0 timeout. There cannot be zero timeouts, passing zero means
244    no timeout is used for the task and SILC_TASK_FD_TASK is used as
245    default task type in this case.
246    
247    One should be careful not to register timeout tasks to the non-timeout
248    task queue, because they will never expire. As one should not register
249    non-timeout tasks to timeout task queue because they will never get
250    scheduled.
251    
252    There is a one distinct difference between timeout and non-timeout
253    tasks when they are executed. Non-timeout tasks remain on the task
254    queue after execution. Timeout tasks, however, are removed from the
255    task queue after they have expired. It is safe to re-register a task 
256    in its own callback function. It is also safe to unregister a task 
257    in a callback function.
258    
259    Generic tasks apply to all file descriptors, however, one still must
260    pass the correct file descriptor to the function when registering
261    generic tasks. */
262
263 SilcTask silc_task_register(SilcTaskQueue queue, int fd, 
264                             SilcTaskCallback cb, void *context, 
265                             long seconds, long useconds, 
266                             SilcTaskType type, SilcTaskPriority priority)
267 {
268   SilcTask new;
269   int timeout = FALSE;
270
271   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", 
272                   fd, type, priority));
273
274   /* If the task is generic task, we check whether this task has already
275      been registered. Generic tasks are registered only once and after that
276      the same task applies to all file descriptors to be registered. */
277   if ((type == SILC_TASK_GENERIC) && queue->task) {
278     SilcTask task;
279
280     task = queue->task;
281     while(1) {
282       if ((task->callback == cb) && (task->context == context)) {
283         SILC_LOG_DEBUG(("Found matching generic task, using the match"));
284
285         /* Add the fd to be listened, the task found now applies to this
286            fd as well. */
287         silc_schedule_set_listen_fd(fd, (1L << SILC_TASK_READ));
288         return task;
289       }
290
291       if (queue->task == task->next)
292         break;
293       
294       task = task->next;
295     }
296   }
297
298   new = silc_calloc(1, sizeof(*new));
299   new->fd = fd;
300   new->context = context;
301   new->callback = cb;
302   new->valid = TRUE;
303   new->priority = priority;
304   new->iomask = (1L << SILC_TASK_READ);
305   new->next = new;
306   new->prev = new;
307
308   /* If the task is non-timeout task we have to tell the scheduler that we
309      would like to have these tasks scheduled at some odd distant future. */
310   if (type != SILC_TASK_TIMEOUT)
311     silc_schedule_set_listen_fd(fd, (1L << SILC_TASK_READ));
312
313   /* Create timeout if marked to be timeout task */
314   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
315     gettimeofday(&new->timeout, NULL);
316     new->timeout.tv_sec += seconds + (useconds / 1000000L);
317     new->timeout.tv_usec += (useconds % 1000000L);
318     if (new->timeout.tv_usec > 999999L) {
319       new->timeout.tv_sec += 1;
320       new->timeout.tv_usec -= 1000000L;
321     }
322     timeout = TRUE;
323   }
324
325   /* Is this first task of the queue? */
326   if (queue->task == NULL) {
327     queue->task = new;
328     return new;
329   }
330
331   if (timeout)
332     return silc_task_add_timeout(queue, new, priority);
333   else
334     return silc_task_add(queue, new, priority);
335 }
336
337 /* Removes (unregisters) a task from particular task queue. This function
338    is used internally by scheduler. One should not call this function
339    to unregister tasks, instead silc_task_unregister_task function
340    should be used. */
341
342 int silc_task_remove(SilcTaskQueue queue, SilcTask task)
343 {
344   SilcTask first, old, next;
345
346   if (!queue || !queue->task)
347     return FALSE;
348
349   first = queue->task;
350
351   /* Unregister all tasks in queue */
352   if (task == SILC_ALL_TASKS) {
353     SILC_LOG_DEBUG(("Removing all tasks at once"));
354     next = first;
355
356     while(1) {
357       next = next->next;
358       silc_free(next->prev);
359       if (next == first)
360         break;
361     }
362
363     queue->task = NULL;
364     return TRUE;
365   }
366
367   SILC_LOG_DEBUG(("Removing task"));
368
369   /* Unregister the task */
370   old = first;
371   while(1) {
372     if (old == task) {
373       SilcTask prev, next;
374
375       prev = old->prev;
376       next = old->next;
377       prev->next = next;
378       next->prev = prev;
379
380       if (prev == old && next == old)
381         queue->task = NULL;
382       if (queue->task == old)
383         queue->task = silc_task_get_first(queue, next);
384       
385       silc_free(old);
386       return TRUE;
387     }
388     old = old->prev;
389
390     if (old == first)
391       return FALSE;
392   }
393 }
394
395 /* Unregisters a task already in the queue. Arguments are as follows:
396    
397    SilcTaskQueue queue      Queue where from the task is unregistered
398    SilcTask task            Task to be unregistered
399    
400    The same function is used to unregister timeout and non-timeout 
401    tasks. One can also unregister all tasks from the queue by passing
402    SILC_ALL_TASKS as task to the function. It is safe to unregister
403    a task in a callback function. */
404
405 void silc_task_unregister(SilcTaskQueue queue, SilcTask task)
406 {
407
408   /* Unregister all tasks */
409   if (task == SILC_ALL_TASKS) {
410     SilcTask next;
411     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
412
413     if (queue->task == NULL)
414       return;
415
416     next = queue->task;
417     
418     while(1) {
419       if (next->valid)
420         next->valid = FALSE;
421       if (queue->task == next->next)
422         break;
423       next = next->next;
424     }
425     return;
426   }
427
428   SILC_LOG_DEBUG(("Unregistering task"));
429
430   /* Unregister the specific task */
431   if (task->valid)
432     task->valid = FALSE;
433 }
434
435 /* Unregister a task by file descriptor. This invalidates the task. */
436
437 void silc_task_unregister_by_fd(SilcTaskQueue queue, int fd)
438 {
439   SilcTask next;
440
441   SILC_LOG_DEBUG(("Unregister task by fd"));
442
443   if (queue->task == NULL)
444     return;
445
446   next = queue->task;
447
448   while(1) {
449     if (next->fd == fd)
450       next->valid = FALSE;
451     if (queue->task == next->next)
452       break;
453     next = next->next;
454   }
455 }
456
457 /* Unregister a task by callback function. This invalidates the task. */
458
459 void silc_task_unregister_by_callback(SilcTaskQueue queue, 
460                                       SilcTaskCallback callback)
461 {
462   SilcTask next;
463
464   SILC_LOG_DEBUG(("Unregister task by callback"));
465
466   if (queue->task == NULL)
467     return;
468
469   next = queue->task;
470
471   while(1) {
472     if (next->callback == callback)
473       next->valid = FALSE;
474     if (queue->task == next->next)
475       break;
476     next = next->next;
477   }
478 }
479
480 /* Unregister a task by context. This invalidates the task. */
481
482 void silc_task_unregister_by_context(SilcTaskQueue queue, void *context)
483 {
484   SilcTask next;
485
486   SILC_LOG_DEBUG(("Unregister task by context"));
487
488   if (queue->task == NULL)
489     return;
490
491   next = queue->task;
492
493   while(1) {
494     if (next->context == context)
495       next->valid = FALSE;
496     if (queue->task == next->next)
497       break;
498     next = next->next;
499   }
500 }
501
502 /* Sets the I/O type of the task. The scheduler checks for this value
503    and a task must always have at least one of the I/O types set at 
504    all time. When registering new task the type is set by default to
505    SILC_TASK_READ. If the task doesn't perform reading one must reset
506    the value to SILC_TASK_WRITE.
507    
508    The type sent as argumenet is masked into the task. If the tasks 
509    I/O mask already includes this type this function has no effect. 
510    Only one I/O type can be added at once. If the task must perform
511    both reading and writing one must call this function for value
512    SILC_TASK_WRITE as well. */
513
514 void silc_task_set_iotype(SilcTask task, int type)
515 {
516   task->iomask |= (1L << type);
517 }
518
519 /* Resets the mask to the type sent as argument. Note that this resets
520    the previous values to zero and then adds the type sent as argument.
521    This function can be used to remove one of the types masked earlier
522    to the task. */
523
524 void silc_task_reset_iotype(SilcTask task, int type)
525 {
526   task->iomask = (1L << type);
527 }
528
529 /* Compare two time values. If the first argument is smaller than the
530    second this function returns TRUE. */
531
532 int silc_task_timeout_compare(struct timeval *smaller, 
533                               struct timeval *bigger)
534 {
535   if ((smaller->tv_sec < bigger->tv_sec) ||
536       ((smaller->tv_sec == bigger->tv_sec) &&
537        (smaller->tv_usec < bigger->tv_usec)))
538     return TRUE;
539
540   return FALSE;
541 }