3853a1bb7f3ca808398699daab1248804153f866
[silc.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) > 0 && !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) <= 0 && 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     silc_free(schedule);
330     return NULL;
331   }
332
333   silc_list_init(schedule->timeout_queue, struct SilcTaskStruct, next);
334   silc_list_init(schedule->free_tasks, struct SilcTaskStruct, next);
335
336   schedule->app_context = app_context;
337   schedule->valid = TRUE;
338   schedule->max_tasks = max_tasks;
339
340   /* Allocate scheduler lock */
341   silc_mutex_alloc(&schedule->lock);
342
343   /* Initialize the platform specific scheduler. */
344   schedule->internal = schedule_ops.init(schedule, app_context);
345   if (!schedule->internal) {
346     silc_hash_table_free(schedule->fd_queue);
347     silc_mutex_free(schedule->lock);
348     silc_free(schedule);
349     return NULL;
350   }
351
352   /* Timeout freelist garbage collection */
353   silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
354                                  schedule, 3600, 0);
355
356   return schedule;
357 }
358
359 /* Uninitializes the schedule. This is called when the program is ready
360    to end. This removes all tasks and task queues. Returns FALSE if the
361    scheduler could not be uninitialized. This happens when the scheduler
362    is still valid and silc_schedule_stop has not been called. */
363
364 SilcBool silc_schedule_uninit(SilcSchedule schedule)
365 {
366   SilcTask task;
367
368   SILC_LOG_DEBUG(("Uninitializing scheduler"));
369
370   if (schedule->valid == TRUE)
371     return FALSE;
372
373   /* Dispatch all timeouts before going away */
374   SILC_SCHEDULE_LOCK(schedule);
375   silc_schedule_dispatch_timeout(schedule, TRUE);
376   SILC_SCHEDULE_UNLOCK(schedule);
377
378   /* Deliver signals before going away */
379   if (schedule->signal_tasks) {
380     schedule_ops.signals_call(schedule, schedule->internal);
381     schedule->signal_tasks = FALSE;
382   }
383
384   /* Unregister all tasks */
385   silc_schedule_task_del(schedule, SILC_ALL_TASKS);
386   silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
387
388   /* Delete timeout task freelist */
389   silc_list_start(schedule->free_tasks);
390   while ((task = silc_list_get(schedule->free_tasks)))
391     silc_free(task);
392
393   /* Unregister all task queues */
394   silc_hash_table_free(schedule->fd_queue);
395
396   /* Uninit the platform specific scheduler. */
397   schedule_ops.uninit(schedule, schedule->internal);
398
399   silc_mutex_free(schedule->lock);
400   silc_free(schedule);
401
402   return TRUE;
403 }
404
405 /* Stops the schedule even if it is not supposed to be stopped yet.
406    After calling this, one should call silc_schedule_uninit (after the
407    silc_schedule has returned). */
408
409 void silc_schedule_stop(SilcSchedule schedule)
410 {
411   SILC_LOG_DEBUG(("Stopping scheduler"));
412   SILC_SCHEDULE_LOCK(schedule);
413   schedule->valid = FALSE;
414   SILC_SCHEDULE_UNLOCK(schedule);
415 }
416
417 /* Runs the scheduler once and then returns.   Must be called locked. */
418
419 static SilcBool silc_schedule_iterate(SilcSchedule schedule, int timeout_usecs)
420 {
421   struct timeval timeout;
422   int ret;
423
424   do {
425     SILC_LOG_DEBUG(("In scheduler loop"));
426
427     /* Deliver signals if any has been set to be called */
428     if (silc_unlikely(schedule->signal_tasks)) {
429       SILC_SCHEDULE_UNLOCK(schedule);
430       schedule_ops.signals_call(schedule, schedule->internal);
431       schedule->signal_tasks = FALSE;
432       SILC_SCHEDULE_LOCK(schedule);
433     }
434
435     /* Check if scheduler is valid */
436     if (silc_unlikely(schedule->valid == FALSE)) {
437       SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
438       return FALSE;
439     }
440
441     /* Calculate next timeout for silc_select().  This is the timeout value
442        when at earliest some of the timeout tasks expire.  This may dispatch
443        already expired timeouts. */
444     silc_schedule_select_timeout(schedule);
445
446     /* Check if scheduler is valid */
447     if (silc_unlikely(schedule->valid == FALSE)) {
448       SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
449       return FALSE;
450     }
451
452     if (timeout_usecs >= 0) {
453       timeout.tv_sec = 0;
454       timeout.tv_usec = timeout_usecs;
455       schedule->timeout = timeout;
456       schedule->has_timeout = TRUE;
457     }
458
459     /* This is the main silc_select(). The program blocks here until some
460        of the selected file descriptors change status or the selected
461        timeout expires. */
462     SILC_LOG_DEBUG(("Select"));
463     ret = schedule_ops.schedule(schedule, schedule->internal);
464
465     if (silc_likely(ret == 0)) {
466       /* Timeout */
467       SILC_LOG_DEBUG(("Running timeout tasks"));
468       if (silc_likely(silc_list_count(schedule->timeout_queue)))
469         silc_schedule_dispatch_timeout(schedule, FALSE);
470       continue;
471
472     } else if (silc_likely(ret > 0)) {
473       /* There is some data available now */
474       SILC_LOG_DEBUG(("Running fd tasks"));
475       silc_schedule_dispatch_fd(schedule);
476       continue;
477
478     } else {
479       /* Error */
480       if (silc_likely(errno == EINTR))
481         continue;
482       SILC_LOG_ERROR(("Error in select()/poll(): %s", strerror(errno)));
483       continue;
484     }
485   } while (timeout_usecs == -1);
486
487   return TRUE;
488 }
489
490 /* Runs the scheduler once and then returns. */
491
492 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
493 {
494   SilcBool ret;
495   SILC_SCHEDULE_LOCK(schedule);
496   ret = silc_schedule_iterate(schedule, timeout_usecs);
497   SILC_SCHEDULE_UNLOCK(schedule);
498   return ret;
499 }
500
501 /* Runs the scheduler and blocks here.  When this returns the scheduler
502    has ended. */
503
504 void silc_schedule(SilcSchedule schedule)
505 {
506   SILC_LOG_DEBUG(("Running scheduler"));
507
508   /* Start the scheduler loop */
509   SILC_SCHEDULE_LOCK(schedule);
510   silc_schedule_iterate(schedule, -1);
511   SILC_SCHEDULE_UNLOCK(schedule);
512 }
513
514 /* Wakes up the scheduler. This is used only in multi-threaded
515    environments where threads may add new tasks or remove old tasks
516    from task queues. This is called to wake up the scheduler in the
517    main thread so that it detects the changes in the task queues.
518    If threads support is not compiled in this function has no effect.
519    Implementation of this function is platform specific. */
520
521 void silc_schedule_wakeup(SilcSchedule schedule)
522 {
523 #ifdef SILC_THREADS
524   SILC_LOG_DEBUG(("Wakeup scheduler"));
525   SILC_SCHEDULE_LOCK(schedule);
526   schedule_ops.wakeup(schedule, schedule->internal);
527   SILC_SCHEDULE_UNLOCK(schedule);
528 #endif
529 }
530
531 /* Returns the application specific context that was saved into the
532    scheduler in silc_schedule_init function.  The context is also
533    returned to application in task callback functions, but this function
534    may be used to get it as well if needed. */
535
536 void *silc_schedule_get_context(SilcSchedule schedule)
537 {
538   return schedule->app_context;
539 }
540
541 /* Add new task to the scheduler */
542
543 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
544                                 SilcTaskCallback callback, void *context,
545                                 long seconds, long useconds,
546                                 SilcTaskType type)
547 {
548   SilcTask task = NULL;
549
550   if (silc_unlikely(!schedule->valid))
551     return NULL;
552
553   SILC_SCHEDULE_LOCK(schedule);
554
555   if (silc_likely(type == SILC_TASK_TIMEOUT)) {
556     SilcTaskTimeout tmp, prev, ttask;
557     SilcList list;
558
559     silc_list_start(schedule->free_tasks);
560     ttask = silc_list_get(schedule->free_tasks);
561     if (silc_unlikely(!ttask)) {
562       ttask = silc_calloc(1, sizeof(*ttask));
563       if (silc_unlikely(!ttask))
564         goto out;
565     } else
566       silc_list_del(schedule->free_tasks, ttask);
567
568     ttask->header.type = 1;
569     ttask->header.callback = callback;
570     ttask->header.context = context;
571     ttask->header.valid = TRUE;
572
573     /* Add timeout */
574     silc_gettimeofday(&ttask->timeout);
575     if ((seconds + useconds) > 0) {
576       ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
577       ttask->timeout.tv_usec += (useconds % 1000000L);
578       if (ttask->timeout.tv_usec >= 1000000L) {
579         ttask->timeout.tv_sec += 1;
580         ttask->timeout.tv_usec -= 1000000L;
581       }
582     }
583
584     SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
585                     seconds, useconds));
586
587     /* Add task to correct spot so that the first task in the list has
588        the earliest timeout. */
589     list = schedule->timeout_queue;
590     silc_list_start(list);
591     prev = NULL;
592     while ((tmp = silc_list_get(list)) != SILC_LIST_END) {
593       /* If we have shorter timeout, we have found our spot */
594       if (silc_compare_timeval(&ttask->timeout, &tmp->timeout) < 0) {
595         silc_list_insert(schedule->timeout_queue, prev, ttask);
596         break;
597       }
598       prev = tmp;
599     }
600     if (!tmp)
601       silc_list_add(schedule->timeout_queue, ttask);
602
603     task = (SilcTask)ttask;
604
605   } else if (silc_likely(type == SILC_TASK_FD)) {
606     SilcTaskFd ftask;
607
608     /* Check if fd is already added */
609     if (silc_unlikely(silc_hash_table_find(schedule->fd_queue,
610                                            SILC_32_TO_PTR(fd),
611                                            NULL, (void *)&task))) {
612       if (task->valid)
613         goto out;
614
615       /* Remove invalid task.  We must have unique fd key to hash table. */
616       silc_schedule_task_remove(schedule, task);
617     }
618
619     /* Check max tasks */
620     if (silc_unlikely(schedule->max_tasks > 0 &&
621                       silc_hash_table_count(schedule->fd_queue) >=
622                       schedule->max_tasks)) {
623       SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
624       task = NULL;
625       goto out;
626     }
627
628     ftask = silc_calloc(1, sizeof(*ftask));
629     if (silc_unlikely(!ftask)) {
630       task = NULL;
631       goto out;
632     }
633
634     SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
635
636     ftask->header.type = 0;
637     ftask->header.callback = callback;
638     ftask->header.context = context;
639     ftask->header.valid = TRUE;
640     ftask->events = SILC_TASK_READ;
641     ftask->fd = fd;
642
643     /* Add task and schedule it */
644     if (!silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask)) {
645       silc_free(ftask);
646       task = NULL;
647       goto out;
648     }
649     if (!schedule_ops.schedule_fd(schedule, schedule->internal,
650                                   ftask, ftask->events)) {
651       silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd));
652       task = NULL;
653       goto out;
654     }
655
656     task = (SilcTask)ftask;
657
658   } else if (silc_unlikely(type == SILC_TASK_SIGNAL)) {
659     SILC_SCHEDULE_UNLOCK(schedule);
660     schedule_ops.signal_register(schedule, schedule->internal, fd,
661                                  callback, context);
662     return NULL;
663   }
664
665  out:
666   SILC_SCHEDULE_UNLOCK(schedule);
667   return task;
668 }
669
670 /* Invalidates task */
671
672 SilcBool silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
673 {
674   if (silc_unlikely(task == SILC_ALL_TASKS)) {
675     SilcHashTableList htl;
676
677     SILC_LOG_DEBUG(("Unregister all tasks"));
678
679     SILC_SCHEDULE_LOCK(schedule);
680
681     /* Delete from fd queue */
682     silc_hash_table_list(schedule->fd_queue, &htl);
683     while (silc_hash_table_get(&htl, NULL, (void *)&task))
684       task->valid = FALSE;
685     silc_hash_table_list_reset(&htl);
686
687     /* Delete from timeout queue */
688     silc_list_start(schedule->timeout_queue);
689     while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))
690            != SILC_LIST_END)
691       task->valid = FALSE;
692
693     SILC_SCHEDULE_UNLOCK(schedule);
694     return TRUE;
695   }
696
697   SILC_LOG_DEBUG(("Unregistering task %p", task));
698   SILC_SCHEDULE_LOCK(schedule);
699   task->valid = FALSE;
700   SILC_SCHEDULE_UNLOCK(schedule);
701
702   return TRUE;
703 }
704
705 /* Invalidate task by fd */
706
707 SilcBool silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
708 {
709   SilcTask task = NULL;
710   SilcBool ret = FALSE;
711
712   SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
713
714   SILC_SCHEDULE_LOCK(schedule);
715
716   /* fd is unique, so there is only one task with this fd in the table */
717   if (silc_likely(silc_hash_table_find(schedule->fd_queue,
718                                        SILC_32_TO_PTR(fd), NULL,
719                                        (void *)&task))) {
720     SILC_LOG_DEBUG(("Deleting task %p", task));
721     task->valid = FALSE;
722     ret = TRUE;
723   }
724
725   SILC_SCHEDULE_UNLOCK(schedule);
726
727   /* If it is signal, remove it */
728   if (silc_unlikely(!task)) {
729     schedule_ops.signal_unregister(schedule, schedule->internal, fd);
730     ret = TRUE;
731   }
732
733   return ret;
734 }
735
736 /* Invalidate task by task callback. */
737
738 SilcBool silc_schedule_task_del_by_callback(SilcSchedule schedule,
739                                             SilcTaskCallback callback)
740 {
741   SilcTask task;
742   SilcHashTableList htl;
743   SilcList list;
744   SilcBool ret = FALSE;
745
746   SILC_LOG_DEBUG(("Unregister task by callback"));
747
748   SILC_SCHEDULE_LOCK(schedule);
749
750   /* Delete from fd queue */
751   silc_hash_table_list(schedule->fd_queue, &htl);
752   while (silc_hash_table_get(&htl, NULL, (void *)&task)) {
753     if (task->callback == callback) {
754       task->valid = FALSE;
755       ret = TRUE;
756     }
757   }
758   silc_hash_table_list_reset(&htl);
759
760   /* Delete from timeout queue */
761   list = schedule->timeout_queue;
762   silc_list_start(list);
763   while ((task = (SilcTask)silc_list_get(list))) {
764     if (task->callback == callback) {
765       task->valid = FALSE;
766       ret = TRUE;
767     }
768   }
769
770   SILC_SCHEDULE_UNLOCK(schedule);
771
772   return ret;
773 }
774
775 /* Invalidate task by context. */
776
777 SilcBool silc_schedule_task_del_by_context(SilcSchedule schedule,
778                                            void *context)
779 {
780   SilcTask task;
781   SilcHashTableList htl;
782   SilcList list;
783   SilcBool ret = FALSE;
784
785   SILC_LOG_DEBUG(("Unregister task by context"));
786
787   SILC_SCHEDULE_LOCK(schedule);
788
789   /* Delete from fd queue */
790   silc_hash_table_list(schedule->fd_queue, &htl);
791   while (silc_hash_table_get(&htl, NULL, (void *)&task)) {
792     if (task->context == context) {
793       task->valid = FALSE;
794       ret = TRUE;
795     }
796   }
797   silc_hash_table_list_reset(&htl);
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->context == context) {
804       ret = TRUE;
805       task->valid = FALSE;
806     }
807   }
808
809   SILC_SCHEDULE_UNLOCK(schedule);
810
811   return ret;
812 }
813
814 /* Invalidate task by all */
815
816 SilcBool silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
817                                        SilcTaskCallback callback,
818                                        void *context)
819 {
820   SilcTask task;
821   SilcList list;
822   SilcBool ret = FALSE;
823
824   SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
825
826   /* For fd task, callback and context is irrelevant as fd is unique */
827   if (fd)
828     return silc_schedule_task_del_by_fd(schedule, fd);
829
830   SILC_SCHEDULE_LOCK(schedule);
831
832   /* Delete from timeout queue */
833   list = schedule->timeout_queue;
834   silc_list_start(list);
835   while ((task = (SilcTask)silc_list_get(list))) {
836     if (task->callback == callback && task->context == context) {
837       task->valid = FALSE;
838       ret = TRUE;
839     }
840   }
841
842   SILC_SCHEDULE_UNLOCK(schedule);
843
844   return TRUE;
845 }
846
847 /* Sets a file descriptor to be listened by scheduler. One can call this
848    directly if wanted. This can be called multiple times for one file
849    descriptor to set different iomasks. */
850
851 SilcBool silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
852                                      SilcTaskEvent mask, SilcBool send_events)
853 {
854   SilcTaskFd task;
855
856   if (silc_unlikely(!schedule->valid))
857     return FALSE;
858
859   SILC_SCHEDULE_LOCK(schedule);
860
861   if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
862                            NULL, (void *)&task)) {
863     if (!schedule_ops.schedule_fd(schedule, schedule->internal, task, mask)) {
864       SILC_SCHEDULE_UNLOCK(schedule);
865       return FALSE;
866     }
867     task->events = mask;
868     if (silc_unlikely(send_events) && mask) {
869       task->revents = mask;
870       silc_schedule_dispatch_fd(schedule);
871     }
872   }
873
874   SILC_SCHEDULE_UNLOCK(schedule);
875
876   return TRUE;
877 }
878
879 /* Returns the file descriptor's current requested event mask. */
880
881 SilcTaskEvent silc_schedule_get_fd_events(SilcSchedule schedule,
882                                           SilcUInt32 fd)
883 {
884   SilcTaskFd task;
885   SilcTaskEvent event = 0;
886
887   if (silc_unlikely(!schedule->valid))
888     return 0;
889
890   SILC_SCHEDULE_LOCK(schedule);
891   if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
892                            NULL, (void *)&task))
893     event = task->events;
894   SILC_SCHEDULE_UNLOCK(schedule);
895
896   return event;
897 }
898
899 /* Removes a file descriptor from listen list. */
900
901 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
902 {
903   silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);
904 }