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