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