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