Added SILC Thread Queue API
[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 /* Connected event context */
26 typedef struct SilcScheduleEventConnectionStruct {
27   SilcSchedule schedule;
28   SilcTaskEventCallback callback;
29   void *context;
30   struct SilcScheduleEventConnectionStruct *next;
31 } *SilcScheduleEventConnection;
32
33 /* Platform specific implementation */
34 extern const SilcScheduleOps schedule_ops;
35
36 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task);
37 static void silc_schedule_dispatch_fd(SilcSchedule schedule);
38 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
39                                            SilcBool dispatch_all);
40 SILC_TASK_CALLBACK(silc_schedule_event_del_timeout);
41
42 /************************ Static utility functions **************************/
43
44 /* Fd task hash table destructor */
45
46 static void silc_schedule_fd_destructor(void *key, void *context,
47                                         void *user_context)
48 {
49   silc_free(context);
50 }
51
52 /* Executes file descriptor tasks. Invalid tasks are removed here. */
53
54 static void silc_schedule_dispatch_fd(SilcSchedule schedule)
55 {
56   SilcTaskFd task;
57   SilcTask t;
58
59   /* The dispatch list includes only valid tasks, and tasks that have
60      something to dispatch.  Dispatching is atomic; no matter if another
61      thread invalidates a task when we unlock, we dispatch to completion. */
62   SILC_SCHEDULE_UNLOCK(schedule);
63   silc_list_start(schedule->fd_dispatch);
64   while ((task = silc_list_get(schedule->fd_dispatch))) {
65     t = (SilcTask)task;
66
67     /* Is the task ready for reading */
68     if (task->revents & SILC_TASK_READ)
69       t->callback(schedule, schedule->app_context, SILC_TASK_READ,
70                   task->fd, t->context);
71
72     /* Is the task ready for writing */
73     if (t->valid && task->revents & SILC_TASK_WRITE)
74       t->callback(schedule, schedule->app_context, SILC_TASK_WRITE,
75                   task->fd, t->context);
76   }
77   SILC_SCHEDULE_LOCK(schedule);
78
79   /* Remove invalidated tasks */
80   silc_list_start(schedule->fd_dispatch);
81   while ((task = silc_list_get(schedule->fd_dispatch)))
82     if (silc_unlikely(!task->header.valid))
83       silc_schedule_task_remove(schedule, (SilcTask)task);
84 }
85
86 /* Executes all tasks whose timeout has expired. The task is removed from
87    the task queue after the callback function has returned. Also, invalid
88    tasks are removed here. */
89
90 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
91                                            SilcBool dispatch_all)
92 {
93   SilcTask t;
94   SilcTaskTimeout task;
95   struct timeval curtime;
96   int count = 0;
97
98   SILC_LOG_DEBUG(("Running timeout tasks"));
99
100   silc_gettimeofday(&curtime);
101
102   /* First task in the task queue has always the earliest timeout. */
103   silc_list_start(schedule->timeout_queue);
104   task = silc_list_get(schedule->timeout_queue);
105   if (silc_unlikely(!task))
106     return;
107   do {
108     t = (SilcTask)task;
109
110     /* Remove invalid task */
111     if (silc_unlikely(!t->valid)) {
112       silc_schedule_task_remove(schedule, t);
113       continue;
114     }
115
116     /* Execute the task if the timeout has expired */
117     if (silc_compare_timeval(&task->timeout, &curtime) > 0 && !dispatch_all)
118       break;
119
120     t->valid = FALSE;
121     SILC_SCHEDULE_UNLOCK(schedule);
122     t->callback(schedule, schedule->app_context, SILC_TASK_EXPIRE, 0,
123                 t->context);
124     SILC_SCHEDULE_LOCK(schedule);
125
126     /* Remove the expired task */
127     silc_schedule_task_remove(schedule, t);
128
129     /* Balance when we have lots of small timeouts */
130     if (silc_unlikely((++count) > 40))
131       break;
132   } while (silc_likely((task = silc_list_get(schedule->timeout_queue))));
133 }
134
135 /* Calculates next timeout. This is the timeout value when at earliest some
136    of the timeout tasks expire. If this is in the past, they will be
137    dispatched now. */
138
139 static void silc_schedule_select_timeout(SilcSchedule schedule)
140 {
141   SilcTask t;
142   SilcTaskTimeout task;
143   struct timeval curtime;
144   SilcBool dispatch = TRUE;
145
146   /* Get the current time */
147   silc_gettimeofday(&curtime);
148   schedule->has_timeout = FALSE;
149
150   /* First task in the task queue has always the earliest timeout. */
151   silc_list_start(schedule->timeout_queue);
152   task = silc_list_get(schedule->timeout_queue);
153   if (silc_unlikely(!task))
154     return;
155   do {
156     t = (SilcTask)task;
157
158     /* Remove invalid task */
159     if (silc_unlikely(!t->valid)) {
160       silc_schedule_task_remove(schedule, t);
161       continue;
162     }
163
164     /* If the timeout is in past, we will run the task and all other
165        timeout tasks from the past. */
166     if (silc_compare_timeval(&task->timeout, &curtime) <= 0 && dispatch) {
167       silc_schedule_dispatch_timeout(schedule, FALSE);
168       if (silc_unlikely(!schedule->valid))
169         return;
170
171       /* Start selecting new timeout again after dispatch */
172       silc_list_start(schedule->timeout_queue);
173       dispatch = FALSE;
174       continue;
175     }
176
177     /* Calculate the next timeout */
178     curtime.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
179     curtime.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
180     if (curtime.tv_sec < 0)
181       curtime.tv_sec = 0;
182
183     /* We wouldn't want to go under zero, check for it. */
184     if (curtime.tv_usec < 0) {
185       curtime.tv_sec -= 1;
186       if (curtime.tv_sec < 0)
187         curtime.tv_sec = 0;
188       curtime.tv_usec += 1000000L;
189     }
190     break;
191   } while ((task = silc_list_get(schedule->timeout_queue)));
192
193   /* Save the timeout */
194   if (task) {
195     schedule->timeout = curtime;
196     schedule->has_timeout = TRUE;
197     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout.tv_sec,
198                     schedule->timeout.tv_usec));
199   }
200 }
201
202 /* Removes task from the scheduler.  This must be called with scheduler
203    locked. */
204
205 static void silc_schedule_task_remove(SilcSchedule schedule, SilcTask task)
206 {
207   SilcSchedule parent;
208
209   if (silc_unlikely(task == SILC_ALL_TASKS)) {
210     SilcTask task;
211     SilcEventTask etask;
212     SilcHashTableList htl;
213     void *fd;
214
215     /* Delete from fd queue */
216     silc_hash_table_list(schedule->fd_queue, &htl);
217     while (silc_hash_table_get(&htl, &fd, (void *)&task))
218       silc_hash_table_del(schedule->fd_queue, fd);
219     silc_hash_table_list_reset(&htl);
220
221     /* Delete from timeout queue */
222     silc_list_start(schedule->timeout_queue);
223     while ((task = silc_list_get(schedule->timeout_queue))) {
224       silc_list_del(schedule->timeout_queue, task);
225       silc_free(task);
226     }
227
228     /* Delete even tasks */
229     parent = silc_schedule_get_parent(schedule);
230     silc_hash_table_list(parent->events, &htl);
231     while (silc_hash_table_get(&htl, NULL, (void *)&etask)) {
232       silc_hash_table_del_by_context(parent->events, etask->event, etask);
233       silc_free(etask->event);
234       silc_free(etask);
235     }
236     silc_hash_table_list_reset(&htl);
237     return;
238   }
239
240   switch (task->type) {
241   case SILC_TASK_FD:
242     {
243       /* Delete from fd queue */
244       SilcTaskFd ftask = (SilcTaskFd)task;
245       silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(ftask->fd));
246     }
247     break;
248
249   case SILC_TASK_TIMEOUT:
250     {
251       /* Delete from timeout queue */
252       silc_list_del(schedule->timeout_queue, task);
253
254       /* Put to free list */
255       silc_list_add(schedule->free_tasks, task);
256     }
257     break;
258
259   case SILC_TASK_EVENT:
260     {
261       SilcEventTask etask = (SilcEventTask)task;
262       SilcScheduleEventConnection conn;
263
264       parent = silc_schedule_get_parent(schedule);
265
266       /* Delete event */
267       silc_hash_table_del_by_context(parent->events, etask->event, etask);
268
269       /* Remove all connections */
270       silc_list_start(etask->connections);
271       while ((conn = silc_list_get(etask->connections)))
272         silc_free(conn);
273
274       silc_free(etask->event);
275       silc_free(etask);
276     }
277     break;
278
279   default:
280     break;
281   }
282 }
283
284 /* Timeout freelist garbage collection */
285
286 SILC_TASK_CALLBACK(silc_schedule_timeout_gc)
287 {
288   SilcTaskTimeout t;
289   int c;
290
291   if (!schedule->valid)
292     return;
293
294   SILC_LOG_DEBUG(("Timeout freelist garbage collection"));
295
296   SILC_SCHEDULE_LOCK(schedule);
297
298   if (silc_list_count(schedule->free_tasks) <= 10) {
299     SILC_SCHEDULE_UNLOCK(schedule);
300     silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
301                                    schedule, 3600, 0);
302     return;
303   }
304   if (silc_list_count(schedule->timeout_queue) >
305       silc_list_count(schedule->free_tasks)) {
306     SILC_SCHEDULE_UNLOCK(schedule);
307     silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
308                                    schedule, 3600, 0);
309     return;
310   }
311
312   c = silc_list_count(schedule->free_tasks) / 2;
313   if (c > silc_list_count(schedule->timeout_queue))
314     c = (silc_list_count(schedule->free_tasks) -
315          silc_list_count(schedule->timeout_queue));
316   if (silc_list_count(schedule->free_tasks) - c < 10)
317     c -= (10 - (silc_list_count(schedule->free_tasks) - c));
318
319   SILC_LOG_DEBUG(("Freeing %d unused tasks, leaving %d", c,
320                   silc_list_count(schedule->free_tasks) - c));
321
322   silc_list_start(schedule->free_tasks);
323   while ((t = silc_list_get(schedule->free_tasks)) && c-- > 0) {
324     silc_list_del(schedule->free_tasks, t);
325     silc_free(t);
326   }
327   silc_list_start(schedule->free_tasks);
328
329   SILC_SCHEDULE_UNLOCK(schedule);
330
331   silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
332                                  schedule, 3600, 0);
333 }
334
335 #ifdef SILC_DIST_INPLACE
336 /* Print schedule statistics to stdout */
337
338 void silc_schedule_stats(SilcSchedule schedule)
339 {
340   SilcTaskFd ftask;
341   fprintf(stdout, "Schedule %p statistics:\n\n", schedule);
342   fprintf(stdout, "Num FD tasks         : %d (%lu bytes allocated)\n",
343           silc_hash_table_count(schedule->fd_queue),
344           sizeof(*ftask) * silc_hash_table_count(schedule->fd_queue));
345   fprintf(stdout, "Num Timeout tasks    : %d (%lu bytes allocated)\n",
346           silc_list_count(schedule->timeout_queue),
347           sizeof(struct SilcTaskTimeoutStruct) *
348           silc_list_count(schedule->timeout_queue));
349   fprintf(stdout, "Num Timeout freelist : %d (%lu bytes allocated)\n",
350           silc_list_count(schedule->free_tasks),
351           sizeof(struct SilcTaskTimeoutStruct) *
352           silc_list_count(schedule->free_tasks));
353 }
354 #endif /* SILC_DIST_INPLACE */
355
356 /****************************** Public API **********************************/
357
358 /* Initializes the scheduler. This returns the scheduler context that
359    is given as arugment usually to all silc_schedule_* functions.
360    The `max_tasks' indicates the number of maximum tasks that the
361    scheduler can handle. The `app_context' is application specific
362    context that is delivered to task callbacks. */
363
364 SilcSchedule silc_schedule_init(int max_tasks, void *app_context,
365                                 SilcStack stack, SilcSchedule parent)
366 {
367   SilcSchedule schedule;
368
369   /* Initialize Tls, in case it hasn't been done yet */
370   silc_thread_tls_init();
371
372   stack = silc_stack_alloc(0, stack);
373   if (!stack)
374     return NULL;
375
376   /* Allocate scheduler from the stack */
377   schedule = silc_scalloc(stack, 1, sizeof(*schedule));
378   if (!schedule)
379     return NULL;
380
381   SILC_LOG_DEBUG(("Initializing scheduler %p", schedule));
382
383   /* Allocate Fd task hash table dynamically */
384   schedule->fd_queue =
385     silc_hash_table_alloc(NULL, 0, silc_hash_uint, NULL, NULL, NULL,
386                           silc_schedule_fd_destructor, NULL, TRUE);
387   if (!schedule->fd_queue) {
388     silc_stack_free(stack);
389     return NULL;
390   }
391
392   silc_list_init(schedule->timeout_queue, struct SilcTaskStruct, next);
393   silc_list_init(schedule->free_tasks, struct SilcTaskStruct, next);
394
395   /* Get the parent */
396   if (parent && parent->parent)
397     parent = parent->parent;
398
399   schedule->stack = stack;
400   schedule->app_context = app_context;
401   schedule->valid = TRUE;
402   schedule->max_tasks = max_tasks;
403   schedule->parent = parent;
404
405   /* Allocate scheduler lock */
406   silc_mutex_alloc(&schedule->lock);
407
408   /* Initialize the platform specific scheduler. */
409   schedule->internal = schedule_ops.init(schedule, app_context);
410   if (!schedule->internal) {
411     silc_hash_table_free(schedule->fd_queue);
412     silc_mutex_free(schedule->lock);
413     silc_stack_free(stack);
414     return NULL;
415   }
416
417   /* Timeout freelist garbage collection */
418   silc_schedule_task_add_timeout(schedule, silc_schedule_timeout_gc,
419                                  schedule, 3600, 0);
420
421   return schedule;
422 }
423
424 /* Uninitializes the schedule. This is called when the program is ready
425    to end. This removes all tasks and task queues. Returns FALSE if the
426    scheduler could not be uninitialized. This happens when the scheduler
427    is still valid and silc_schedule_stop has not been called. */
428
429 SilcBool silc_schedule_uninit(SilcSchedule schedule)
430 {
431   SilcTask task;
432
433   SILC_VERIFY(schedule);
434
435   SILC_LOG_DEBUG(("Uninitializing scheduler %p", schedule));
436
437   if (schedule->valid == TRUE)
438     return FALSE;
439
440   /* Dispatch all timeouts before going away */
441   SILC_SCHEDULE_LOCK(schedule);
442   silc_schedule_dispatch_timeout(schedule, TRUE);
443   SILC_SCHEDULE_UNLOCK(schedule);
444
445   /* Deliver signals before going away */
446   if (schedule->signal_tasks) {
447     schedule_ops.signals_call(schedule, schedule->internal);
448     schedule->signal_tasks = FALSE;
449   }
450
451   /* Unregister all tasks */
452   silc_schedule_task_del(schedule, SILC_ALL_TASKS);
453   silc_schedule_task_remove(schedule, SILC_ALL_TASKS);
454
455   /* Delete timeout task freelist */
456   silc_list_start(schedule->free_tasks);
457   while ((task = silc_list_get(schedule->free_tasks)))
458     silc_free(task);
459
460   /* Unregister all task queues */
461   silc_hash_table_free(schedule->fd_queue);
462
463   /* Uninit the platform specific scheduler. */
464   schedule_ops.uninit(schedule, schedule->internal);
465
466   silc_mutex_free(schedule->lock);
467   silc_stack_free(schedule->stack);
468
469   return TRUE;
470 }
471
472 /* Stops the schedule even if it is not supposed to be stopped yet.
473    After calling this, one should call silc_schedule_uninit (after the
474    silc_schedule has returned). */
475
476 void silc_schedule_stop(SilcSchedule schedule)
477 {
478   SILC_LOG_DEBUG(("Stopping scheduler"));
479   SILC_VERIFY(schedule);
480   SILC_SCHEDULE_LOCK(schedule);
481   schedule->valid = FALSE;
482   SILC_SCHEDULE_UNLOCK(schedule);
483 }
484
485 /* Runs the scheduler once and then returns.   Must be called locked. */
486
487 static SilcBool silc_schedule_iterate(SilcSchedule schedule, int timeout_usecs)
488 {
489   struct timeval timeout;
490   int ret;
491
492   do {
493     SILC_LOG_DEBUG(("In scheduler loop"));
494
495     /* Deliver signals if any has been set to be called */
496     if (silc_unlikely(schedule->signal_tasks)) {
497       SILC_SCHEDULE_UNLOCK(schedule);
498       schedule_ops.signals_call(schedule, schedule->internal);
499       schedule->signal_tasks = FALSE;
500       SILC_SCHEDULE_LOCK(schedule);
501     }
502
503     /* Check if scheduler is valid */
504     if (silc_unlikely(schedule->valid == FALSE)) {
505       SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
506       return FALSE;
507     }
508
509     /* Calculate next timeout for silc_select().  This is the timeout value
510        when at earliest some of the timeout tasks expire.  This may dispatch
511        already expired timeouts. */
512     silc_schedule_select_timeout(schedule);
513
514     /* Check if scheduler is valid */
515     if (silc_unlikely(schedule->valid == FALSE)) {
516       SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
517       return FALSE;
518     }
519
520     if (timeout_usecs >= 0) {
521       timeout.tv_sec = 0;
522       timeout.tv_usec = timeout_usecs;
523       schedule->timeout = timeout;
524       schedule->has_timeout = TRUE;
525     }
526
527     /* This is the main silc_select(). The program blocks here until some
528        of the selected file descriptors change status or the selected
529        timeout expires. */
530     SILC_LOG_DEBUG(("Select"));
531     ret = schedule_ops.schedule(schedule, schedule->internal);
532
533     if (silc_likely(ret == 0)) {
534       /* Timeout */
535       SILC_LOG_DEBUG(("Running timeout tasks"));
536       if (silc_likely(silc_list_count(schedule->timeout_queue)))
537         silc_schedule_dispatch_timeout(schedule, FALSE);
538       continue;
539
540     } else if (silc_likely(ret > 0)) {
541       /* There is some data available now */
542       SILC_LOG_DEBUG(("Running fd tasks"));
543       silc_schedule_dispatch_fd(schedule);
544
545       /* If timeout was very short, dispatch also timeout tasks */
546       if (schedule->has_timeout && schedule->timeout.tv_sec == 0 &&
547           schedule->timeout.tv_usec < 50000)
548         silc_schedule_dispatch_timeout(schedule, FALSE);
549       continue;
550
551     } else {
552       /* Error or special case handling */
553       if (errno == EINTR)
554         continue;
555       if (ret == -2)
556         break;
557
558       SILC_LOG_ERROR(("Error in select()/poll(): %s", strerror(errno)));
559       continue;
560     }
561   } while (timeout_usecs == -1);
562
563   return TRUE;
564 }
565
566 /* Runs the scheduler once and then returns. */
567
568 SilcBool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
569 {
570   SilcBool ret;
571   SILC_SCHEDULE_LOCK(schedule);
572   ret = silc_schedule_iterate(schedule, timeout_usecs);
573   SILC_SCHEDULE_UNLOCK(schedule);
574   return ret;
575 }
576
577 /* Runs the scheduler and blocks here.  When this returns the scheduler
578    has ended. */
579
580 #ifndef SILC_SYMBIAN
581 void silc_schedule(SilcSchedule schedule)
582 {
583   SILC_LOG_DEBUG(("Running scheduler"));
584
585   /* Start the scheduler loop */
586   SILC_SCHEDULE_LOCK(schedule);
587   silc_schedule_iterate(schedule, -1);
588   SILC_SCHEDULE_UNLOCK(schedule);
589 }
590 #endif /* !SILC_SYMBIAN */
591
592 /* Wakes up the scheduler. This is used only in multi-threaded
593    environments where threads may add new tasks or remove old tasks
594    from task queues. This is called to wake up the scheduler in the
595    main thread so that it detects the changes in the task queues.
596    If threads support is not compiled in this function has no effect.
597    Implementation of this function is platform specific. */
598
599 void silc_schedule_wakeup(SilcSchedule schedule)
600 {
601 #ifdef SILC_THREADS
602   SILC_LOG_DEBUG(("Wakeup scheduler"));
603   SILC_SCHEDULE_LOCK(schedule);
604   schedule_ops.wakeup(schedule, schedule->internal);
605   SILC_SCHEDULE_UNLOCK(schedule);
606 #endif
607 }
608
609 /* Returns parent scheduler */
610
611 SilcSchedule silc_schedule_get_parent(SilcSchedule schedule)
612 {
613   return schedule->parent ? schedule->parent : schedule;
614 }
615
616 /* Returns the application specific context that was saved into the
617    scheduler in silc_schedule_init function.  The context is also
618    returned to application in task callback functions, but this function
619    may be used to get it as well if needed. */
620
621 void *silc_schedule_get_context(SilcSchedule schedule)
622 {
623   return schedule->app_context;
624 }
625
626 /* Return the stack of the scheduler */
627
628 SilcStack silc_schedule_get_stack(SilcSchedule schedule)
629 {
630   return schedule->stack;
631 }
632
633 /* Set notify callback */
634
635 void silc_schedule_set_notify(SilcSchedule schedule,
636                               SilcTaskNotifyCb notify, void *context)
637 {
638   schedule->notify = notify;
639   schedule->notify_context = context;
640 }
641
642 /* Set global scheduler */
643
644 void silc_schedule_set_global(SilcSchedule schedule)
645 {
646   SilcTls tls = silc_thread_get_tls();
647
648   if (!tls) {
649     /* Try to initialize Tls */
650     tls = silc_thread_tls_init();
651     SILC_VERIFY(tls);
652     if (!tls)
653       return;
654   }
655
656   SILC_LOG_DEBUG(("Setting global scheduler %p", schedule));
657
658   tls->schedule = schedule;
659 }
660
661 /* Return global scheduler */
662
663 SilcSchedule silc_schedule_get_global(void)
664 {
665   SilcTls tls = silc_thread_get_tls();
666
667   if (!tls)
668     return NULL;
669
670   SILC_LOG_DEBUG(("Return global scheduler %p", tls->schedule));
671
672   return tls->schedule;
673 }
674
675 /* Add new task to the scheduler */
676
677 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
678                                 SilcTaskCallback callback, void *context,
679                                 long seconds, long useconds,
680                                 SilcTaskType type)
681 {
682   SilcTask task = NULL;
683
684   if (!schedule) {
685     schedule = silc_schedule_get_global();
686     SILC_VERIFY(schedule);
687     if (!schedule) {
688       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
689       return NULL;
690     }
691   }
692
693   if (silc_unlikely(!schedule->valid)) {
694     silc_set_errno(SILC_ERR_NOT_VALID);
695     return NULL;
696   }
697
698   SILC_SCHEDULE_LOCK(schedule);
699
700   if (silc_likely(type == SILC_TASK_TIMEOUT)) {
701     SilcTaskTimeout tmp, prev, ttask;
702     SilcList list;
703
704     silc_list_start(schedule->free_tasks);
705     ttask = silc_list_get(schedule->free_tasks);
706     if (silc_unlikely(!ttask)) {
707       ttask = silc_calloc(1, sizeof(*ttask));
708       if (silc_unlikely(!ttask))
709         goto out;
710     } else
711       silc_list_del(schedule->free_tasks, ttask);
712
713     ttask->header.type = 1;
714     ttask->header.callback = callback;
715     ttask->header.context = context;
716     ttask->header.valid = TRUE;
717
718     /* Add timeout */
719     silc_gettimeofday(&ttask->timeout);
720     if ((seconds + useconds) > 0) {
721       ttask->timeout.tv_sec += seconds + (useconds / 1000000L);
722       ttask->timeout.tv_usec += (useconds % 1000000L);
723       if (ttask->timeout.tv_usec >= 1000000L) {
724         ttask->timeout.tv_sec += 1;
725         ttask->timeout.tv_usec -= 1000000L;
726       }
727     }
728
729     SILC_LOG_DEBUG(("New timeout task %p: sec=%d, usec=%d", ttask,
730                     seconds, useconds));
731
732     /* Add task to correct spot so that the first task in the list has
733        the earliest timeout. */
734     list = schedule->timeout_queue;
735     silc_list_start(list);
736     prev = NULL;
737     while ((tmp = silc_list_get(list)) != SILC_LIST_END) {
738       /* If we have shorter timeout, we have found our spot */
739       if (silc_compare_timeval(&ttask->timeout, &tmp->timeout) < 0) {
740         silc_list_insert(schedule->timeout_queue, prev, ttask);
741         break;
742       }
743       prev = tmp;
744     }
745     if (!tmp)
746       silc_list_add(schedule->timeout_queue, ttask);
747
748     task = (SilcTask)ttask;
749
750     /* Call notify callback */
751     if (schedule->notify)
752       schedule->notify(schedule, TRUE, task, FALSE, 0, 0, seconds, useconds,
753                        schedule->notify_context);
754
755   } else if (silc_likely(type == SILC_TASK_FD)) {
756     SilcTaskFd ftask;
757
758     /* Check if fd is already added */
759     if (silc_unlikely(silc_hash_table_find(schedule->fd_queue,
760                                            SILC_32_TO_PTR(fd),
761                                            NULL, (void *)&task))) {
762       if (task->valid)
763         goto out;
764
765       /* Remove invalid task.  We must have unique fd key to hash table. */
766       silc_schedule_task_remove(schedule, task);
767     }
768
769     /* Check max tasks */
770     if (silc_unlikely(schedule->max_tasks > 0 &&
771                       silc_hash_table_count(schedule->fd_queue) >=
772                       schedule->max_tasks)) {
773       SILC_LOG_WARNING(("Scheduler task limit reached: cannot add new task"));
774       task = NULL;
775       silc_set_errno(SILC_ERR_LIMIT);
776       goto out;
777     }
778
779     ftask = silc_calloc(1, sizeof(*ftask));
780     if (silc_unlikely(!ftask)) {
781       task = NULL;
782       goto out;
783     }
784
785     SILC_LOG_DEBUG(("New fd task %p fd=%d", ftask, fd));
786
787     ftask->header.type = 0;
788     ftask->header.callback = callback;
789     ftask->header.context = context;
790     ftask->header.valid = TRUE;
791     ftask->events = SILC_TASK_READ;
792     ftask->fd = fd;
793
794     /* Add task and schedule it */
795     if (!silc_hash_table_add(schedule->fd_queue, SILC_32_TO_PTR(fd), ftask)) {
796       silc_free(ftask);
797       task = NULL;
798       goto out;
799     }
800     if (!schedule_ops.schedule_fd(schedule, schedule->internal,
801                                   ftask, ftask->events)) {
802       silc_hash_table_del(schedule->fd_queue, SILC_32_TO_PTR(fd));
803       task = NULL;
804       goto out;
805     }
806
807     task = (SilcTask)ftask;
808
809     /* Call notify callback */
810     if (schedule->notify)
811       schedule->notify(schedule, TRUE, task, TRUE, ftask->fd,
812                        SILC_TASK_READ, 0, 0, schedule->notify_context);
813
814   } else if (silc_unlikely(type == SILC_TASK_SIGNAL)) {
815     SILC_SCHEDULE_UNLOCK(schedule);
816     schedule_ops.signal_register(schedule, schedule->internal, fd,
817                                  callback, context);
818     return NULL;
819   }
820
821  out:
822   SILC_SCHEDULE_UNLOCK(schedule);
823
824 #ifdef SILC_SYMBIAN
825   /* On symbian we wakeup scheduler immediately after adding timeout task
826      in case the task is added outside the scheduler loop (in some active
827      object). */
828   if (task && task->type == 1)
829     silc_schedule_wakeup(schedule);
830 #endif /* SILC_SYMBIAN */
831
832   return task;
833 }
834
835 /* Invalidates task */
836
837 SilcBool silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
838 {
839   SilcSchedule parent;
840
841   if (!schedule) {
842     schedule = silc_schedule_get_global();
843     SILC_VERIFY(schedule);
844     if (!schedule) {
845       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
846       return FALSE;
847     }
848   }
849
850   if (silc_unlikely(task == SILC_ALL_TASKS)) {
851     SilcHashTableList htl;
852
853     SILC_LOG_DEBUG(("Unregister all tasks"));
854
855     SILC_SCHEDULE_LOCK(schedule);
856
857     /* Delete from fd queue */
858     silc_hash_table_list(schedule->fd_queue, &htl);
859     while (silc_hash_table_get(&htl, NULL, (void *)&task)) {
860       task->valid = FALSE;
861
862       /* Call notify callback */
863       if (schedule->notify)
864         schedule->notify(schedule, FALSE, task, TRUE,
865                          ((SilcTaskFd)task)->fd, 0, 0, 0,
866                          schedule->notify_context);
867     }
868     silc_hash_table_list_reset(&htl);
869
870     /* Delete from timeout queue */
871     silc_list_start(schedule->timeout_queue);
872     while ((task = (SilcTask)silc_list_get(schedule->timeout_queue))) {
873       task->valid = FALSE;
874
875       /* Call notify callback */
876       if (schedule->notify)
877         schedule->notify(schedule, FALSE, task, FALSE, 0, 0, 0, 0,
878                          schedule->notify_context);
879     }
880
881     /* Delete even tasks */
882     parent = silc_schedule_get_parent(schedule);
883     silc_hash_table_list(parent->events, &htl);
884     while (silc_hash_table_get(&htl, NULL, (void *)&task))
885       task->valid = FALSE;
886     silc_hash_table_list_reset(&htl);
887
888     SILC_SCHEDULE_UNLOCK(schedule);
889     return TRUE;
890   }
891
892   SILC_LOG_DEBUG(("Unregistering task %p, type %d", task, task->type));
893   SILC_SCHEDULE_LOCK(schedule);
894   task->valid = FALSE;
895
896   /* Call notify callback */
897   if (schedule->notify && task->type != SILC_TASK_EVENT)
898     schedule->notify(schedule, FALSE, task, task->type == SILC_TASK_FD,
899                      0, 0, 0, 0, schedule->notify_context);
900   SILC_SCHEDULE_UNLOCK(schedule);
901
902   if (task->type == SILC_TASK_EVENT) {
903     /* Schedule removal of deleted event task */
904     parent = silc_schedule_get_parent(schedule);
905     silc_schedule_task_add_timeout(parent, silc_schedule_event_del_timeout,
906                                    task, 0, 1);
907   }
908
909   return TRUE;
910 }
911
912 /* Invalidate task by fd */
913
914 SilcBool silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
915 {
916   SilcTask task = NULL;
917   SilcBool ret = FALSE;
918
919   SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
920
921   if (!schedule) {
922     schedule = silc_schedule_get_global();
923     SILC_VERIFY(schedule);
924     if (!schedule) {
925       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
926       return FALSE;
927     }
928   }
929
930   SILC_SCHEDULE_LOCK(schedule);
931
932   /* fd is unique, so there is only one task with this fd in the table */
933   if (silc_likely(silc_hash_table_find(schedule->fd_queue,
934                                        SILC_32_TO_PTR(fd), NULL,
935                                        (void *)&task))) {
936     SILC_LOG_DEBUG(("Deleting task %p", task));
937     task->valid = FALSE;
938
939     /* Call notify callback */
940     if (schedule->notify)
941       schedule->notify(schedule, FALSE, task, TRUE, fd, 0, 0, 0,
942                        schedule->notify_context);
943     ret = TRUE;
944   }
945
946   SILC_SCHEDULE_UNLOCK(schedule);
947
948   /* If it is signal, remove it */
949   if (silc_unlikely(!task)) {
950     schedule_ops.signal_unregister(schedule, schedule->internal, fd);
951     ret = TRUE;
952   }
953
954   if (ret == FALSE)
955     silc_set_errno(SILC_ERR_NOT_FOUND);
956
957   return ret;
958 }
959
960 /* Invalidate task by task callback. */
961
962 SilcBool silc_schedule_task_del_by_callback(SilcSchedule schedule,
963                                             SilcTaskCallback callback)
964 {
965   SilcTask task;
966   SilcHashTableList htl;
967   SilcList list;
968   SilcBool ret = FALSE;
969
970   SILC_LOG_DEBUG(("Unregister task by callback"));
971
972   if (!schedule) {
973     schedule = silc_schedule_get_global();
974     SILC_VERIFY(schedule);
975     if (!schedule) {
976       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
977       return FALSE;
978     }
979   }
980
981   SILC_SCHEDULE_LOCK(schedule);
982
983   /* Delete from fd queue */
984   silc_hash_table_list(schedule->fd_queue, &htl);
985   while (silc_hash_table_get(&htl, NULL, (void *)&task)) {
986     if (task->callback == callback) {
987       task->valid = FALSE;
988
989       /* Call notify callback */
990       if (schedule->notify)
991         schedule->notify(schedule, FALSE, task, TRUE,
992                          ((SilcTaskFd)task)->fd, 0, 0, 0,
993                          schedule->notify_context);
994       ret = TRUE;
995     }
996   }
997   silc_hash_table_list_reset(&htl);
998
999   /* Delete from timeout queue */
1000   list = schedule->timeout_queue;
1001   silc_list_start(list);
1002   while ((task = (SilcTask)silc_list_get(list))) {
1003     if (task->callback == callback) {
1004       task->valid = FALSE;
1005
1006       /* Call notify callback */
1007       if (schedule->notify)
1008         schedule->notify(schedule, FALSE, task, FALSE, 0, 0, 0, 0,
1009                          schedule->notify_context);
1010       ret = TRUE;
1011     }
1012   }
1013
1014   SILC_SCHEDULE_UNLOCK(schedule);
1015
1016   if (ret == FALSE)
1017     silc_set_errno(SILC_ERR_NOT_FOUND);
1018
1019   return ret;
1020 }
1021
1022 /* Invalidate task by context. */
1023
1024 SilcBool silc_schedule_task_del_by_context(SilcSchedule schedule,
1025                                            void *context)
1026 {
1027   SilcTask task;
1028   SilcHashTableList htl;
1029   SilcList list;
1030   SilcBool ret = FALSE;
1031
1032   SILC_LOG_DEBUG(("Unregister task by context"));
1033
1034   if (!schedule) {
1035     schedule = silc_schedule_get_global();
1036     SILC_VERIFY(schedule);
1037     if (!schedule) {
1038       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1039       return FALSE;
1040     }
1041   }
1042
1043   SILC_SCHEDULE_LOCK(schedule);
1044
1045   /* Delete from fd queue */
1046   silc_hash_table_list(schedule->fd_queue, &htl);
1047   while (silc_hash_table_get(&htl, NULL, (void *)&task)) {
1048     if (task->context == context) {
1049       task->valid = FALSE;
1050
1051       /* Call notify callback */
1052       if (schedule->notify)
1053         schedule->notify(schedule, FALSE, task, TRUE,
1054                          ((SilcTaskFd)task)->fd, 0, 0, 0,
1055                          schedule->notify_context);
1056       ret = TRUE;
1057     }
1058   }
1059   silc_hash_table_list_reset(&htl);
1060
1061   /* Delete from timeout queue */
1062   list = schedule->timeout_queue;
1063   silc_list_start(list);
1064   while ((task = (SilcTask)silc_list_get(list))) {
1065     if (task->context == context) {
1066       task->valid = FALSE;
1067
1068       /* Call notify callback */
1069       if (schedule->notify)
1070         schedule->notify(schedule, FALSE, task, FALSE, 0, 0, 0, 0,
1071                          schedule->notify_context);
1072       ret = TRUE;
1073     }
1074   }
1075
1076   SILC_SCHEDULE_UNLOCK(schedule);
1077
1078   if (ret == FALSE)
1079     silc_set_errno(SILC_ERR_NOT_FOUND);
1080
1081   return ret;
1082 }
1083
1084 /* Invalidate task by all */
1085
1086 SilcBool silc_schedule_task_del_by_all(SilcSchedule schedule, int fd,
1087                                        SilcTaskCallback callback,
1088                                        void *context)
1089 {
1090   SilcTask task;
1091   SilcList list;
1092   SilcBool ret = FALSE;
1093
1094   SILC_LOG_DEBUG(("Unregister task by fd, callback and context"));
1095
1096   /* For fd task, callback and context is irrelevant as fd is unique */
1097   if (fd)
1098     return silc_schedule_task_del_by_fd(schedule, fd);
1099
1100   if (!schedule) {
1101     schedule = silc_schedule_get_global();
1102     SILC_VERIFY(schedule);
1103     if (!schedule) {
1104       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1105       return FALSE;
1106     }
1107   }
1108
1109   SILC_SCHEDULE_LOCK(schedule);
1110
1111   /* Delete from timeout queue */
1112   list = schedule->timeout_queue;
1113   silc_list_start(list);
1114   while ((task = (SilcTask)silc_list_get(list))) {
1115     if (task->callback == callback && task->context == context) {
1116       task->valid = FALSE;
1117
1118       /* Call notify callback */
1119       if (schedule->notify)
1120         schedule->notify(schedule, FALSE, task, FALSE, 0, 0, 0, 0,
1121                          schedule->notify_context);
1122       ret = TRUE;
1123     }
1124   }
1125
1126   SILC_SCHEDULE_UNLOCK(schedule);
1127
1128   if (ret == FALSE)
1129     silc_set_errno(SILC_ERR_NOT_FOUND);
1130
1131   return TRUE;
1132 }
1133
1134 /* Sets a file descriptor to be listened by scheduler. One can call this
1135    directly if wanted. This can be called multiple times for one file
1136    descriptor to set different iomasks. */
1137
1138 SilcBool silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
1139                                      SilcTaskEvent mask, SilcBool send_events)
1140 {
1141   SilcTaskFd task;
1142
1143   if (!schedule) {
1144     schedule = silc_schedule_get_global();
1145     SILC_VERIFY(schedule);
1146     if (!schedule) {
1147       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1148       return FALSE;
1149     }
1150   }
1151
1152   if (silc_unlikely(!schedule->valid)) {
1153     silc_set_errno(SILC_ERR_NOT_VALID);
1154     return FALSE;
1155   }
1156
1157   SILC_SCHEDULE_LOCK(schedule);
1158
1159   if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
1160                            NULL, (void *)&task)) {
1161     if (!schedule_ops.schedule_fd(schedule, schedule->internal, task, mask)) {
1162       SILC_SCHEDULE_UNLOCK(schedule);
1163       return FALSE;
1164     }
1165     task->events = mask;
1166     if (silc_unlikely(send_events) && mask) {
1167       task->revents = mask;
1168       silc_schedule_dispatch_fd(schedule);
1169     }
1170
1171     /* Call notify callback */
1172     if (schedule->notify)
1173       schedule->notify(schedule, TRUE, (SilcTask)task,
1174                        TRUE, task->fd, mask, 0, 0,
1175                        schedule->notify_context);
1176   }
1177
1178   SILC_SCHEDULE_UNLOCK(schedule);
1179
1180   return TRUE;
1181 }
1182
1183 /* Returns the file descriptor's current requested event mask. */
1184
1185 SilcTaskEvent silc_schedule_get_fd_events(SilcSchedule schedule,
1186                                           SilcUInt32 fd)
1187 {
1188   SilcTaskFd task;
1189   SilcTaskEvent event = 0;
1190
1191   if (!schedule) {
1192     schedule = silc_schedule_get_global();
1193     SILC_VERIFY(schedule);
1194     if (!schedule) {
1195       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1196       return 0;
1197     }
1198   }
1199
1200   if (silc_unlikely(!schedule->valid)) {
1201     silc_set_errno(SILC_ERR_NOT_VALID);
1202     return 0;
1203   }
1204
1205   SILC_SCHEDULE_LOCK(schedule);
1206   if (silc_hash_table_find(schedule->fd_queue, SILC_32_TO_PTR(fd),
1207                            NULL, (void *)&task))
1208     event = task->events;
1209   SILC_SCHEDULE_UNLOCK(schedule);
1210
1211   return event;
1212 }
1213
1214 /* Removes a file descriptor from listen list. */
1215
1216 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
1217 {
1218   silc_schedule_set_listen_fd(schedule, fd, 0, FALSE);
1219 }
1220
1221 /*************************** Asynchronous Events ****************************/
1222
1223 /* Add event */
1224
1225 SilcTask silc_schedule_task_add_event(SilcSchedule schedule,
1226                                       const char *event, ...)
1227 {
1228   SilcEventTask task;
1229   SilcSchedule parent;
1230
1231   if (!schedule) {
1232     schedule = silc_schedule_get_global();
1233     SILC_VERIFY(schedule);
1234     if (!schedule) {
1235       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1236       return NULL;
1237     }
1238   }
1239
1240   /* Get parent scheduler */
1241   parent = silc_schedule_get_parent(schedule);
1242
1243   SILC_LOG_DEBUG(("Adding event '%s' to scheduler %p", event, parent));
1244
1245   SILC_SCHEDULE_LOCK(parent);
1246
1247   /* Create events hash table if not already done */
1248   if (!parent->events) {
1249     parent->events = silc_hash_table_alloc(NULL, 3,
1250                                            silc_hash_string, NULL,
1251                                            silc_hash_string_compare, NULL,
1252                                            NULL, NULL, FALSE);
1253     if (!parent->events) {
1254       SILC_SCHEDULE_UNLOCK(parent);
1255       return NULL;
1256     }
1257   }
1258
1259   /* Check if this event is added already */
1260   if (silc_hash_table_find(parent->events, (void *)event, NULL, NULL)) {
1261     SILC_SCHEDULE_UNLOCK(parent);
1262     return NULL;
1263   }
1264
1265   /* Add new event */
1266   task = silc_calloc(1, sizeof(*task));
1267   if (!task) {
1268     SILC_SCHEDULE_UNLOCK(parent);
1269     return NULL;
1270   }
1271
1272   task->header.type = SILC_TASK_EVENT;
1273   task->header.valid = TRUE;
1274   task->event = silc_strdup(event);
1275   if (!task->event) {
1276     SILC_SCHEDULE_UNLOCK(parent);
1277     silc_free(task);
1278     return NULL;
1279   }
1280   silc_list_init(task->connections, struct SilcScheduleEventConnectionStruct,
1281                  next);
1282
1283   if (!silc_hash_table_add(parent->events, task->event, task)) {
1284     SILC_SCHEDULE_UNLOCK(parent);
1285     silc_free(task->event);
1286     silc_free(task);
1287     return NULL;
1288   }
1289
1290   SILC_SCHEDULE_UNLOCK(parent);
1291
1292   return (SilcTask)task;
1293 }
1294
1295 /* Connect to event task */
1296
1297 SilcBool silc_schedule_event_connect(SilcSchedule schedule,
1298                                      const char *event, SilcTask task,
1299                                      SilcTaskEventCallback callback,
1300                                      void *context)
1301 {
1302   SilcSchedule parent;
1303   SilcScheduleEventConnection conn;
1304   SilcEventTask etask;
1305
1306   if (!schedule) {
1307     schedule = silc_schedule_get_global();
1308     SILC_VERIFY(schedule);
1309     if (!schedule) {
1310       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1311       return FALSE;
1312     }
1313   }
1314
1315   if (!event && !task) {
1316     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1317     return FALSE;
1318   }
1319
1320   if (task && task->type != SILC_TASK_EVENT) {
1321     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1322     return FALSE;
1323   }
1324
1325   /* Get parent scheduler */
1326   parent = silc_schedule_get_parent(schedule);
1327
1328   SILC_SCHEDULE_LOCK(parent);
1329
1330   if (!task) {
1331     /* Get the event task */
1332     if (!silc_hash_table_find(parent->events, (void *)event, NULL,
1333                               (void *)&task)) {
1334       SILC_SCHEDULE_UNLOCK(parent);
1335       return FALSE;
1336     }
1337   }
1338   etask = (SilcEventTask)task;
1339
1340   /* See if task is deleted */
1341   if (task->valid == FALSE) {
1342     SILC_SCHEDULE_UNLOCK(parent);
1343     silc_set_errno(SILC_ERR_NOT_VALID);
1344     return FALSE;
1345   }
1346
1347   SILC_LOG_DEBUG(("Connect callback %p with context %p to event '%s'",
1348                   callback, context, etask->event));
1349
1350   /* See if already connected */
1351   silc_list_start(etask->connections);
1352   while ((conn = silc_list_get(etask->connections))) {
1353     if (conn->callback == callback && conn->context == context) {
1354       SILC_SCHEDULE_UNLOCK(parent);
1355       silc_set_errno(SILC_ERR_ALREADY_EXISTS);
1356       return FALSE;
1357     }
1358   }
1359
1360   conn = silc_calloc(1, sizeof(*conn));
1361   if (!conn) {
1362     SILC_SCHEDULE_UNLOCK(parent);
1363     return FALSE;
1364   }
1365
1366   /* Connect to the event */
1367   conn->schedule = schedule;
1368   conn->callback = callback;
1369   conn->context = context;
1370   silc_list_add(etask->connections, conn);
1371
1372   SILC_SCHEDULE_UNLOCK(parent);
1373
1374   return TRUE;
1375 }
1376
1377 /* Disconnect from event */
1378
1379 SilcBool silc_schedule_event_disconnect(SilcSchedule schedule,
1380                                         const char *event, SilcTask task,
1381                                         SilcTaskEventCallback callback,
1382                                         void *context)
1383 {
1384   SilcSchedule parent;
1385   SilcScheduleEventConnection conn;
1386   SilcEventTask etask;
1387
1388   if (!schedule) {
1389     schedule = silc_schedule_get_global();
1390     SILC_VERIFY(schedule);
1391     if (!schedule) {
1392       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1393       return FALSE;
1394     }
1395   }
1396
1397   if (!event && !task) {
1398     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1399     return FALSE;
1400   }
1401
1402   if (task && task->type != SILC_TASK_EVENT) {
1403     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1404     return FALSE;
1405   }
1406
1407   /* Get parent scheduler */
1408   parent = silc_schedule_get_parent(schedule);
1409
1410   SILC_SCHEDULE_LOCK(parent);
1411
1412   if (!task) {
1413     /* Get the event task */
1414     if (!silc_hash_table_find(parent->events, (void *)event, NULL,
1415                               (void *)&task)) {
1416       SILC_SCHEDULE_UNLOCK(parent);
1417       return FALSE;
1418     }
1419   }
1420   etask = (SilcEventTask)task;
1421
1422   /* See if task is deleted */
1423   if (task->valid == FALSE) {
1424     SILC_SCHEDULE_UNLOCK(parent);
1425     silc_set_errno(SILC_ERR_NOT_VALID);
1426     return FALSE;
1427   }
1428
1429   SILC_LOG_DEBUG(("Disconnect callback %p with context %p from event '%s'",
1430                   callback, context, etask->event));
1431
1432   /* Disconnect */
1433   silc_list_start(etask->connections);
1434   while ((conn = silc_list_get(etask->connections))) {
1435     if (conn->callback == callback && conn->context == context) {
1436       silc_list_del(etask->connections, conn);
1437       silc_free(conn);
1438       SILC_SCHEDULE_UNLOCK(parent);
1439       return TRUE;
1440     }
1441   }
1442
1443   SILC_SCHEDULE_UNLOCK(parent);
1444   silc_set_errno(SILC_ERR_NOT_FOUND);
1445   return FALSE;
1446 }
1447
1448 /* Signal event */
1449
1450 SilcBool silc_schedule_event_signal(SilcSchedule schedule, const char *event,
1451                                     SilcTask task, ...)
1452 {
1453   SilcSchedule parent;
1454   SilcScheduleEventConnection conn;
1455   SilcEventTask etask;
1456   SilcBool stop;
1457   va_list ap, cp;
1458
1459   if (silc_unlikely(!schedule)) {
1460     schedule = silc_schedule_get_global();
1461     SILC_VERIFY(schedule);
1462     if (!schedule) {
1463       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1464       return FALSE;
1465     }
1466   }
1467
1468   if (silc_unlikely(!event && !task)) {
1469     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1470     return FALSE;
1471   }
1472
1473   if (silc_unlikely(task && task->type != SILC_TASK_EVENT)) {
1474     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1475     return FALSE;
1476   }
1477
1478   /* Get parent scheduler */
1479   parent = silc_schedule_get_parent(schedule);
1480
1481   SILC_SCHEDULE_LOCK(parent);
1482
1483   if (!task) {
1484     /* Get the event task */
1485     if (!silc_hash_table_find(parent->events, (void *)event, NULL,
1486                               (void *)&task)) {
1487       SILC_SCHEDULE_UNLOCK(parent);
1488       return FALSE;
1489     }
1490   }
1491   etask = (SilcEventTask)task;
1492
1493   /* See if task is deleted */
1494   if (task->valid == FALSE) {
1495     SILC_SCHEDULE_UNLOCK(parent);
1496     silc_set_errno(SILC_ERR_NOT_VALID);
1497     return FALSE;
1498   }
1499
1500   SILC_LOG_DEBUG(("Signal event '%s'", etask->event));
1501
1502   va_start(ap, task);
1503
1504   /* Deliver the signal */
1505   silc_list_start(etask->connections);
1506   while ((conn = silc_list_get(etask->connections))) {
1507     SILC_SCHEDULE_UNLOCK(parent);
1508
1509     silc_va_copy(cp, ap);
1510     stop = conn->callback(conn->schedule, conn->schedule->app_context,
1511                           task, conn->context, cp);
1512     va_end(cp);
1513
1514     SILC_SCHEDULE_LOCK(parent);
1515
1516     /* Stop signal if wanted or if the task was deleted */
1517     if (!stop || !task->valid)
1518       break;
1519   }
1520
1521   va_end(ap);
1522
1523   SILC_SCHEDULE_UNLOCK(parent);
1524
1525   return TRUE;
1526 }
1527
1528 /* Delete event */
1529
1530 SilcBool silc_schedule_task_del_event(SilcSchedule schedule, const char *event)
1531 {
1532   SilcSchedule parent;
1533   SilcTask task;
1534
1535   if (!schedule) {
1536     schedule = silc_schedule_get_global();
1537     SILC_VERIFY(schedule);
1538     if (!schedule) {
1539       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1540       return FALSE;
1541     }
1542   }
1543
1544   if (!event) {
1545     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
1546     return FALSE;
1547   }
1548
1549   /* Get parent scheduler */
1550   parent = silc_schedule_get_parent(schedule);
1551
1552   SILC_SCHEDULE_LOCK(parent);
1553
1554   /* Get the event task */
1555   if (!silc_hash_table_find(parent->events, (void *)event, NULL,
1556                             (void *)&task)) {
1557     SILC_SCHEDULE_UNLOCK(parent);
1558     return FALSE;
1559   }
1560
1561   /* See if already deleted */
1562   if (task->valid == FALSE)
1563     return TRUE;
1564
1565   SILC_LOG_DEBUG(("Delete event '%s'", ((SilcEventTask)task)->event));
1566
1567   SILC_SCHEDULE_UNLOCK(parent);
1568
1569   silc_schedule_task_del(parent, task);
1570
1571   return TRUE;
1572 }
1573
1574 /* Timeout to remove deleted event task */
1575
1576 SILC_TASK_CALLBACK(silc_schedule_event_del_timeout)
1577 {
1578   SILC_SCHEDULE_LOCK(schedule);
1579   silc_schedule_task_remove(schedule, context);
1580   SILC_SCHEDULE_UNLOCK(schedule);
1581 }