Return FD task pointer if already added with same fd and is still
[crypto.git] / lib / silcutil / silcschedule.c
1 /*
2
3   silcschedule.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1998 - 2007 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; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /* $Id$ */
20
21 #include "silc.h"
22
23 /************************** Types and definitions ***************************/
24
25 /* Platform specific implementation */
26 extern const SilcScheduleOps schedule_ops;
27
28 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task);
29 static void silc_schedule_dispatch_fd(SilcSchedule schedule);
30 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
31                                            SilcBool dispatch_all);
32
33
34 /************************ Static utility functions **************************/
35
36 /* Fd task hash table destructor */
37
38 static void silc_schedule_fd_destructor(void *key, void *context,
39                                         void *user_context)
40 {
41   silc_free(context);
42 }
43
44 /* Executes file descriptor tasks. Invalid tasks are removed here. */
45
46 static void silc_schedule_dispatch_fd(SilcSchedule schedule)
47 {
48   SilcTaskFd task;
49   SilcTask t;
50
51   /* The dispatch list includes only valid tasks, and tasks that have
52      something to dispatch.  Dispatching is atomic; no matter if another
53      thread invalidates a task when we unlock, we dispatch to completion. */
54   SILC_SCHEDULE_UNLOCK(schedule);
55   silc_list_start(schedule->fd_dispatch);
56   while ((task = silc_list_get(schedule->fd_dispatch))) {
57     t = (SilcTask)task;
58
59     /* Is the task ready for reading */
60     if (task->revents & SILC_TASK_READ)
61       t->callback(schedule, schedule->app_context, SILC_TASK_READ,
62                   task->fd, t->context);
63
64     /* Is the task ready for writing */
65     if (t->valid && task->revents & SILC_TASK_WRITE)
66       t->callback(schedule, schedule->app_context, SILC_TASK_WRITE,
67                   task->fd, t->context);
68   }
69   SILC_SCHEDULE_LOCK(schedule);
70
71   /* Remove invalidated tasks */
72   silc_list_start(schedule->fd_dispatch);
73   while ((task = silc_list_get(schedule->fd_dispatch)))
74     if (silc_unlikely(!task->header.valid))
75       silc_schedule_task_remove(schedule, (SilcTask)task);
76 }
77
78 /* Executes all tasks whose timeout has expired. The task is removed from
79    the task queue after the callback function has returned. Also, invalid
80    tasks are removed here. */
81
82 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
83                                            SilcBool dispatch_all)
84 {
85   SilcTask t;
86   SilcTaskTimeout task;
87   struct timeval curtime;
88   int count = 0;
89
90   SILC_LOG_DEBUG(("Running timeout tasks"));
91
92   silc_gettimeofday(&curtime);
93
94   /* First task in the task queue has always the earliest timeout. */
95   silc_list_start(schedule->timeout_queue);
96   task = silc_list_get(schedule->timeout_queue);
97   if (silc_unlikely(!task))
98     return;
99   do {
100     t = (SilcTask)task;
101
102     /* Remove invalid task */
103     if (silc_unlikely(!t->valid)) {
104       silc_schedule_task_remove(schedule, t);
105       continue;
106     }
107
108     /* Execute the task if the timeout has expired */
109     if (!silc_compare_timeval(&task->timeout, &curtime) && !dispatch_all)
110       break;
111
112     t->valid = FALSE;
113     SILC_SCHEDULE_UNLOCK(schedule);
114     t->callback(schedule, schedule->app_context, SILC_TASK_EXPIRE, 0,
115                 t->context);
116     SILC_SCHEDULE_LOCK(schedule);
117
118     /* Remove the expired task */
119     silc_schedule_task_remove(schedule, t);
120
121     /* Balance when we have lots of small timeouts */
122     if (silc_unlikely((++count) > 40))
123       break;
124   } while (silc_likely((task = silc_list_get(schedule->timeout_queue))));
125 }
126
127 /* Calculates next timeout. This is the timeout value when at earliest some
128    of the timeout tasks expire. If this is in the past, they will be
129    dispatched now. */
130
131 static void silc_schedule_select_timeout(SilcSchedule schedule)
132 {
133   SilcTask t;
134   SilcTaskTimeout task;
135   struct timeval curtime;
136   SilcBool dispatch = TRUE;
137
138   /* Get the current time */
139   silc_gettimeofday(&curtime);
140   schedule->has_timeout = FALSE;
141
142   /* First task in the task queue has always the earliest timeout. */
143   silc_list_start(schedule->timeout_queue);
144   task = silc_list_get(schedule->timeout_queue);
145   if (silc_unlikely(!task))
146     return;
147   do {
148     t = (SilcTask)task;
149
150     /* Remove invalid task */
151     if (silc_unlikely(!t->valid)) {
152       silc_schedule_task_remove(schedule, t);
153       continue;
154     }
155
156     /* If the timeout is in past, we will run the task and all other
157        timeout tasks from the past. */
158     if (silc_compare_timeval(&task->timeout, &curtime) && dispatch) {
159       silc_schedule_dispatch_timeout(schedule, FALSE);
160       if (silc_unlikely(!schedule->valid))
161         return;
162
163       /* Start selecting new timeout again after dispatch */
164       silc_list_start(schedule->timeout_queue);
165       dispatch = FALSE;
166       continue;
167     }
168
169     /* Calculate the next timeout */
170     curtime.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
171     curtime.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
172     if (curtime.tv_sec < 0)
173       curtime.tv_sec = 0;
174
175     /* We wouldn't want to go under zero, check for it. */
176     if (curtime.tv_usec < 0) {
177       curtime.tv_sec -= 1;
178       if (curtime.tv_sec < 0)
179         curtime.tv_sec = 0;
180       curtime.tv_usec += 1000000L;
181     }
182     break;
183   } while ((task = silc_list_get(schedule->timeout_queue)));
184
185   /* Save the timeout */
186   if (task) {
187     schedule->timeout = curtime;
188     schedule->has_timeout = TRUE;
189     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout.tv_sec,
190                     schedule->timeout.tv_usec));
191   }
192 }
193
194 /* Removes task from the scheduler.  This must be called with scheduler
195    locked. */
196
197 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task)
198 {
199   SilcTaskFd ftask;
200
201   if (silc_unlikely(task == SILC_ALL_TASKS)) {
202     SilcTask task;
203     SilcHashTableList htl;
204     SilcUInt32 fd;
205
206     /* Delete from fd queue */
207     silc_hash_table_list(schedule->fd_queue, &htl);
208     while (silc_hash_table_get(&htl, (void **)&fd, (void **)&task))
209       silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd));
210     silc_hash_table_list_reset(&htl);
211
212     /* Delete from timeout queue */
213     silc_list_start(schedule->timeout_queue);
214     while ((task = silc_list_get(schedule->timeout_queue))) {
215       silc_list_del(schedule->timeout_queue, task);
216       silc_free(task);
217     }
218
219     return;
220   }
221
222   if (silc_likely(task->type == 1)) {
223     /* Delete from timeout queue */
224     silc_list_del(schedule->timeout_queue, task);
225
226     /* Put to free list */
227     silc_list_add(schedule->free_tasks, task);
228   } else {
229     /* Delete from fd queue */
230     ftask = (SilcTaskFd)task;
231     silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd));
232   }
233 }
234
235 /* Timeout freelist garbage collection */
236
237 SILC_TASK_CALLBACK(silc_schedule_timeout_gc)
238 {
239   SilcTaskTimeout t;
240   int c;
241
242   if (!schedule->valid)
243     return;
244
245   SILC_LOG_DEBUG(("Timeout freelist garbage collection"));
246
247   SILC_SCHEDULE_LOCK(schedule);
248
249   if (silc_list_count(schedule->free_tasks) <= 10) {
250     SILC_SCHEDULE_UNLOCK(schedule);
251     silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
252                                    schedule, 3600, 0);
253     return;
254   }
255   if (silc_list_count(schedule->timeout_queue) >
256       silc_list_count(schedule->free_tasks)) {
257     SILC_SCHEDULE_UNLOCK(schedule);
258     silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
259                                    schedule, 3600, 0);
260     return;
261   }
262
263   c = silc_list_count(schedule->free_tasks) / 2;
264   if (c > silc_list_count(schedule->timeout_queue))
265     c = (silc_list_count(schedule->free_tasks) -
266          silc_list_count(schedule->timeout_queue));
267   if (silc_list_count(schedule->free_tasks) - c < 10)
268     c -= (10 - (silc_list_count(schedule->free_tasks) - c));
269
270   SILC_LOG_DEBUG(("Freeing %d unused tasks, leaving %d", c,
271                   silc_list_count(schedule->free_tasks) - c));
272
273   silc_list_start(schedule->free_tasks);
274   while ((t = silc_list_get(schedule->free_tasks)) && c-- > 0) {
275     silc_list_del(schedule->free_tasks, t);
276     silc_free(t);
277   }
278   silc_list_start(schedule->free_tasks);
279
280   SILC_SCHEDULE_UNLOCK(schedule);
281
282   silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
283                                  schedule, 3600, 0);
284 }
285
286 #ifdef SILC_DIST_INPLACE
287 /* Print schedule statistics to stdout */
288
289 void silc_schedule_stats(SilcSchedule schedule)
290 {
291   SilcTaskFd ftask;
292   fprintf(stdout, "Schedule %p statistics:\n\n", schedule);
293   fprintf(stdout, "Num FD tasks         : %lu (%lu bytes allocated)\n",
294           silc_hash_table_count(schedule->fd_queue),
295           sizeof(*ftask) * silc_hash_table_count(schedule->fd_queue));
296   fprintf(stdout, "Num Timeout tasks    : %d (%d bytes allocated)\n",
297           silc_list_count(schedule->timeout_queue),
298           sizeof(struct SilcTaskTimeoutStruct) *
299           silc_list_count(schedule->timeout_queue));
300   fprintf(stdout, "Num Timeout freelist : %d (%d bytes allocated)\n",
301           silc_list_count(schedule->free_tasks),
302           sizeof(struct SilcTaskTimeoutStruct) *
303           silc_list_count(schedule->free_tasks));
304 }
305 #endif /* SILC_DIST_INPLACE */
306
307 /****************************** Public API **********************************/
308
309 /* Initializes the scheduler. This returns the scheduler context that
310    is given as arugment usually to all silc_schedule_* functions.
311    The `max_tasks' indicates the number of maximum tasks that the
312    scheduler can handle. The `app_context' is application specific
313    context that is delivered to task callbacks. */
314
315 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
316 {
317   SilcSchedule schedule;
318
319   SILC_LOG_DEBUG(("Initializing scheduler"));
320
321   schedule = silc_calloc(1, sizeof(*schedule));
322   if (!schedule)
323     return NULL;
324
325   schedule->fd_queue =
326     silc_hash_table_alloc(0, silc_hash_uint, NULL, NULL, NULL,
327                           silc_schedule_fd_destructor, NULL, TRUE);
328   if (!schedule->fd_queue)
329     return NULL;
330
331   silc_list_init(schedule->timeout_queue, struct SilcTaskStruct, next);
332   silc_list_init(schedule->free_tasks, struct SilcTaskStruct, next);
333
334   schedule->app_context = app_context;
335   schedule->valid = TRUE;
336   schedule->max_tasks = max_tasks;
337
338   /* Allocate scheduler lock */
339   silc_mutex_alloc(&schedule->lock);
340
341   /* Initialize the platform specific scheduler. */
342   schedule->internal = schedule_ops.init(schedule, app_context);
343
344   /* Timeout freelist garbage collection */
345   silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
346                                  schedule, 3600, 0);
347
348   return schedule;
349 }
350
351 /* Uninitializes the schedule. This is called when the program is ready
352    to end. This removes all tasks and task queues. Returns FALSE if the
353    scheduler could not be uninitialized. This happens when the scheduler
354    is still valid and silc_schedule_stop has not been called. */
355
356 SilcBool silc_schedule_uninit(SilcSchedule schedule)
357 {
358   SilcTask task;
359
360   SILC_LOG_DEBUG(("Uninitializing scheduler"));
361
362   if (schedule->valid == TRUE)
363     return FALSE;
364
365   /* Dispatch all timeouts before going away */
366   SILC_SCHEDULE_LOCK(schedule);
367   silc_schedule_dispatch_timeout(schedule, TRUE);
368   SILC_SCHEDULE_UNLOCK(schedule);
369
370   /* Deliver signals before going away */
371   if (schedule->signal_tasks) {
372     schedule_ops.signals_call(schedule, schedule->internal);
373     schedule->signal_tasks = FALSE;
374   }
375
376   /* Unregister all tasks */
377   silc_schedule_task_del(schedule, SILC_ALL_TASKS);
378   silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
379
380   /* Delete timeout task freelist */
381   silc_list_start(schedule->free_tasks);
382   while ((task = silc_list_get(schedule->free_tasks)))
383     silc_free(task);
384
385   /* Unregister all task queues */
386   silc_hash_table_free(schedule->fd_queue);
387
388   /* Uninit the platform specific scheduler. */
389   schedule_ops.uninit(schedule, schedule->internal);
390
391   silc_mutex_free(schedule->lock);
392   silc_free(schedule);
393
394   return TRUE;
395 }
396
397 /* Stops the schedule even if it is not supposed to be stopped yet.
398    After calling this, one should call silc_schedule_uninit (after the
399    silc_schedule has returned). */
400
401 void silc_schedule_stop(SilcSchedule schedule)
402 {
403   SILC_LOG_DEBUG(("Stopping scheduler"));
404   SILC_SCHEDULE_LOCK(schedule);
405   schedule->valid = FALSE;
406   SILC_SCHEDULE_UNLOCK(schedule);
407 }
408
409 /* Runs the scheduler once and then returns.   Must be called locked. */
410
411 static SilcBool silc_schedule_iterate(SilcSchedule schedule, int timeout_usecs)
412 {
413   struct timeval timeout;
414   int ret;
415
416   do {
417     SILC_LOG_DEBUG(("In scheduler loop"));
418
419     /* Deliver signals if any has been set to be called */
420     if (silc_unlikely(schedule->signal_tasks)) {
421       SILC_SCHEDULE_UNLOCK(schedule);
422       schedule_ops.signals_call(schedule, schedule->internal);
423       schedule->signal_tasks = FALSE;
424       SILC_SCHEDULE_LOCK(schedule);
425     }
426
427     /* Check if scheduler is valid */
428     if (silc_unlikely(schedule->valid == FALSE)) {
429       SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
430       return FALSE;
431     }
432
433     /* Calculate next timeout for silc_select().  This is the timeout value
434        when at earliest some of the timeout tasks expire.  This may dispatch
435        already expired timeouts. */
436     silc_schedule_select_timeout(schedule);
437
438     /* Check if scheduler is valid */
439     if (silc_unlikely(schedule->valid == FALSE)) {
440       SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
441       return FALSE;
442     }
443
444     if (timeout_usecs >= 0) {
445       timeout.tv_sec = 0;
446       timeout.tv_usec = timeout_usecs;
447       schedule->timeout = timeout;
448       schedule->has_timeout = TRUE;
449     }
450
451     /* This is the main silc_select(). The program blocks here until some
452        of the selected file descriptors change status or the selected
453        timeout expires. */
454     SILC_LOG_DEBUG(("Select"));
455     ret = schedule_ops.schedule(schedule, schedule->internal);
456
457     if (silc_likely(ret == 0)) {
458       /* Timeout */
459       SILC_LOG_DEBUG(("Running timeout tasks"));
460       if (silc_likely(silc_list_count(schedule->timeout_queue)))
461         silc_schedule_dispatch_timeout(schedule, FALSE);
462       continue;
463
464     } else if (silc_likely(ret > 0)) {
465       /* There is some data available now */
466       SILC_LOG_DEBUG(("Running fd tasks"));
467       silc_schedule_dispatch_fd(schedule);
468       continue;
469
470     } else {
471       /* Error */
472       if (silc_likely(errno == EINTR))
473         continue;
474       SILC_LOG_ERROR(("Error in select()/poll(): %s", strerror(errno)));
475       continue;
476     }
477   } while (timeout_usecs == -1);
478
479   return TRUE;
480 }
481
482 /* Runs the scheduler once and then returns. */
483
484 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
485 {
486   SilcBool ret;
487   SILC_SCHEDULE_LOCK(schedule);
488   ret = silc_schedule_iterate(schedule, timeout_usecs);
489   SILC_SCHEDULE_UNLOCK(schedule);
490   return ret;
491 }
492
493 /* Runs the scheduler and blocks here.  When this returns the scheduler
494    has ended. */
495
496 void silc_schedule(SilcSchedule schedule)
497 {
498   SILC_LOG_DEBUG(("Running scheduler"));
499
500   /* Start the scheduler loop */
501   SILC_SCHEDULE_LOCK(schedule);
502   silc_schedule_iterate(schedule, -1);
503   SILC_SCHEDULE_UNLOCK(schedule);
504 }
505
506 /* Wakes up the scheduler. This is used only in multi-threaded
507    environments where threads may add new tasks or remove old tasks
508    from task queues. This is called to wake up the scheduler in the
509    main thread so that it detects the changes in the task queues.
510    If threads support is not compiled in this function has no effect.
511    Implementation of this function is platform specific. */
512
513 void silc_schedule_wakeup(SilcSchedule schedule)
514 {
515 #ifdef SILC_THREADS
516   SILC_LOG_DEBUG(("Wakeup scheduler"));
517   SILC_SCHEDULE_LOCK(schedule);
518   schedule_ops.wakeup(schedule, schedule->internal);
519   SILC_SCHEDULE_UNLOCK(schedule);
520 #endif
521 }
522
523 /* Returns the application specific context that was saved into the
524    scheduler in silc_schedule_init function.  The context is also
525    returned to application in task callback functions, but this function
526    may be used to get it as well if needed. */
527
528 void *silc_schedule_get_context(SilcSchedule schedule)
529 {
530   return schedule->app_context;
531 }
532
533 /* Add new task to the scheduler */
534
535 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
536                                 SilcTaskCallback callback, void *context,
537                                 long seconds, long useconds,
538                                 SilcTaskType type)
539 {
540   SilcTask task = NULL;
541
542   if (silc_unlikely(!schedule->valid))
543     return NULL;
544
545   SILC_SCHEDULE_LOCK(schedule);
546
547   if (silc_likely(type == SILC_TASK_TIMEOUT)) {
548     SilcTaskTimeout tmp, prev, ttask;
549     SilcList list;
550
551     silc_list_start(schedule->free_tasks);
552     ttask = silc_list_get(schedule->free_tasks);
553     if (silc_unlikely(!ttask)) {
554       ttask = silc_calloc(1, sizeof(*ttask));
555       if (silc_unlikely(!ttask))
556         goto out;
557     }
558     silc_list_del(schedule->free_tasks, ttask);
559
560     ttask->header.type = 1;
561     ttask->header.callback = callback;
562     ttask->header.context = context;
563     ttask->header.valid = TRUE;
564
565     /* Add timeout */
566     silc_gettimeofday(&ttask->timeout);
567     if ((seconds + useconds) > 0) {
568       ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
569       ttask->timeout.tv_usec += (useconds % 1000000L);
570       if (ttask->timeout.tv_usec >= 1000000L) {
571         ttask->timeout.tv_sec += 1;
572         ttask->timeout.tv_usec -= 1000000L;
573       }
574     }
575
576     SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
577                     seconds, useconds));
578
579     /* Add task to correct spot so that the first task in the list has
580        the earliest timeout. */
581     list = schedule->timeout_queue;
582     silc_list_start(list);
583     prev = NULL;
584     while ((tmp = silc_list_get(list)) != SILC_LIST_END) {
585       /* If we have shorter timeout, we have found our spot */
586       if (silc_compare_timeval(&ttask->timeout, &tmp->timeout)) {
587         silc_list_insert(schedule->timeout_queue, prev, ttask);
588         break;
589       }
590       prev = tmp;
591     }
592     if (!tmp)
593       silc_list_add(schedule->timeout_queue, ttask);
594
595     task = (SilcTask)ttask;
596
597   } else if (silc_likely(type == SILC_TASK_FD)) {
598     SilcTaskFd ftask;
599
600     /* Check if fd is already added */
601     if (silc_unlikely(silc_hash_table_find(schedule->fd_queue,
602                                            SILC_32_TO_PTR(fd),
603                                            NULL, (void **)&task))) {
604       if (task->valid)
605         goto out;
606
607       /* Remove invalid task.  We must have unique fd key to hash table. */
608       silc_schedule_task_remove(schedule, task);
609     }
610
611     /* Check max tasks */
612     if (silc_unlikely(schedule->max_tasks > 0 &&
613                       silc_hash_table_count(schedule->fd_queue) >=
614                       schedule->max_tasks)) {
615       SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
616       task = NULL;
617       goto out;
618     }
619
620     ftask = silc_calloc(1, sizeof(*ftask));
621     if (silc_unlikely(!ftask)) {
622       task = NULL;
623       goto out;
624     }
625
626     SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
627
628     ftask->header.type = 0;
629     ftask->header.callback = callback;
630     ftask->header.context = context;
631     ftask->header.valid = TRUE;
632     ftask->events = SILC_TASK_READ;
633     ftask->fd = fd;
634
635     /* Add task and schedule it */
636     if (!silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask)) {
637       silc_free(ftask);
638       task = NULL;
639       goto out;
640     }
641     if (!schedule_ops.schedule_fd(schedule, schedule->internal,
642                                   ftask, ftask->events)) {
643       silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd));
644       task = NULL;
645       goto out;
646     }
647
648     task = (SilcTask)ftask;
649
650   } else if (silc_unlikely(type == SILC_TASK_SIGNAL)) {
651     SILC_SCHEDULE_UNLOCK(schedule);
652     schedule_ops.signal_register(schedule, schedule->internal, fd,
653                                  callback, context);
654     return NULL;
655   }
656
657  out:
658   SILC_SCHEDULE_UNLOCK(schedule);
659   return task;
660 }
661
662 /* Invalidates task */
663
664 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
665 {
666   if (silc_unlikely(task == SILC_ALL_TASKS)) {
667     SilcHashTableList htl;
668
669     SILC_LOG_DEBUG(("Unregister all tasks"));
670
671     SILC_SCHEDULE_LOCK(schedule);
672
673     /* Delete from fd queue */
674     silc_hash_table_list(schedule->fd_queue, &htl);
675     while (silc_hash_table_get(&htl, NULL, (void **)&task))
676       task->valid = FALSE;
677     silc_hash_table_list_reset(&htl);
678
679     /* Delete from timeout queue */
680     silc_list_start(schedule->timeout_queue);
681     while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
682            != SILC_LIST_END)
683       task->valid = FALSE;
684
685     SILC_SCHEDULE_UNLOCK(schedule);
686     return;
687   }
688
689   SILC_LOG_DEBUG(("Unregistering task %p", task));
690   SILC_SCHEDULE_LOCK(schedule);
691   task->valid = FALSE;
692   SILC_SCHEDULE_UNLOCK(schedule);
693 }
694
695 /* Invalidate task by fd */
696
697 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
698 {
699   SilcTask task = NULL;
700
701   SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
702
703   SILC_SCHEDULE_LOCK(schedule);
704
705   /* fd is unique, so there is only one task with this fd in the table */
706   if (silc_likely(silc_hash_table_find(schedule->fd_queue,
707                                        SILC_32_TO_PTR(fd), NULL,
708                                        (void **)&task))) {
709     SILC_LOG_DEBUG(("Deleting task %p", task));
710     task->valid = FALSE;
711   }
712
713   SILC_SCHEDULE_UNLOCK(schedule);
714
715   /* If it is signal, remove it */
716   if (silc_unlikely(!task))
717     schedule_ops.signal_unregister(schedule, schedule->internal, fd);
718 }
719
720 /* Invalidate task by task callback. */
721
722 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
723                                         SilcTaskCallback callback)
724 {
725   SilcTask task;
726   SilcHashTableList htl;
727   SilcList list;
728
729   SILC_LOG_DEBUG(("Unregister task by callback"));
730
731   SILC_SCHEDULE_LOCK(schedule);
732
733   /* Delete from fd queue */
734   silc_hash_table_list(schedule->fd_queue, &htl);
735   while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
736     if (task->callback == callback)
737       task->valid = FALSE;
738   }
739   silc_hash_table_list_reset(&htl);
740
741   /* Delete from timeout queue */
742   list = schedule->timeout_queue;
743   silc_list_start(list);
744   while ((task = (SilcTask)silc_list_get(list))) {
745     if (task->callback == callback)
746       task->valid = FALSE;
747   }
748
749   SILC_SCHEDULE_UNLOCK(schedule);
750 }
751
752 /* Invalidate task by context. */
753
754 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
755 {
756   SilcTask task;
757   SilcHashTableList htl;
758   SilcList list;
759
760   SILC_LOG_DEBUG(("Unregister task by context"));
761
762   SILC_SCHEDULE_LOCK(schedule);
763
764   /* Delete from fd queue */
765   silc_hash_table_list(schedule->fd_queue, &htl);
766   while (silc_hash_table_get(&htl, NULL, (void **)&task)) {
767     if (task->context == context)
768       task->valid = FALSE;
769   }
770   silc_hash_table_list_reset(&htl);
771
772   /* Delete from timeout queue */
773   list = schedule->timeout_queue;
774   silc_list_start(list);
775   while ((task = (SilcTask)silc_list_get(list))) {
776     if (task->context == context)
777       task->valid = FALSE;
778   }
779
780   SILC_SCHEDULE_UNLOCK(schedule);
781 }
782
783 /* Invalidate task by all */
784
785 void silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
786                                    SilcTaskCallback callback, void *context)
787 {
788   SilcTask task;
789   SilcList list;
790
791   SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
792
793   /* For fd task, callback and context is irrelevant as fd is unique */
794   if (fd)
795     silc_schedule_task_del_by_fd(schedule, fd);
796
797   SILC_SCHEDULE_LOCK(schedule);
798
799   /* Delete from timeout queue */
800   list = schedule->timeout_queue;
801   silc_list_start(list);
802   while ((task = (SilcTask)silc_list_get(list))) {
803     if (task->callback == callback && task->context == context)
804       task->valid = FALSE;
805   }
806
807   SILC_SCHEDULE_UNLOCK(schedule);
808 }
809
810 /* Sets a file descriptor to be listened by scheduler. One can call this
811    directly if wanted. This can be called multiple times for one file
812    descriptor to set different iomasks. */
813
814 SilcBool silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
815                                      SilcTaskEvent mask, SilcBool send_events)
816 {
817   SilcTaskFd task;
818
819   if (silc_unlikely(!schedule->valid))
820     return FALSE;
821
822   SILC_SCHEDULE_LOCK(schedule);
823
824   if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
825                            NULL, (void **)&task)) {
826     if (!schedule_ops.schedule_fd(schedule, schedule->internal, task, mask)) {
827       SILC_SCHEDULE_UNLOCK(schedule);
828       return FALSE;
829     }
830     task->events = mask;
831     if (silc_unlikely(send_events) && mask) {
832       task->revents = mask;
833       silc_schedule_dispatch_fd(schedule);
834     }
835   }
836
837   SILC_SCHEDULE_UNLOCK(schedule);
838
839   return TRUE;
840 }
841
842 /* Returns the file descriptor's current requested event mask. */
843
844 SilcTaskEvent silc_schedule_get_fd_events(SilcSchedule schedule,
845                                           SilcUInt32 fd)
846 {
847   SilcTaskFd task;
848   SilcTaskEvent event = 0;
849
850   if (silc_unlikely(!schedule->valid))
851     return 0;
852
853   SILC_SCHEDULE_LOCK(schedule);
854   if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
855                            NULL, (void **)&task))
856     event = task->events;
857   SILC_SCHEDULE_UNLOCK(schedule);
858
859   return event;
860 }
861
862 /* Removes a file descriptor from listen list. */
863
864 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
865 {
866   silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);
867 }