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