Merged from silc_1_0_branch.
[silc.git] / lib / silcutil / silcschedule.c
index 828e78748e639167b95b043064312d28656fb5c7..e0f658b08f52807e42fa93107a14f107155bbf59 100644 (file)
@@ -1,16 +1,15 @@
 /*
 
-  silcschedule.c
+  silcschedule.c 
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1998 - 2001 Pekka Riikonen
+  Copyright (C) 1998 - 2002 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
-  the Free Software Foundation; either version 2 of the License, or
-  (at your option) any later version.
-  
+  the Free Software Foundation; version 2 of the License.
+
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
@@ -35,7 +34,7 @@ int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count,
    the wakeup mechanism of the scheduler.  In multi-threaded environment
    the scheduler needs to be wakenup when tasks are added or removed from
    the task queues.  Returns context to the platform specific scheduler. */
-void *silc_schedule_internal_init(SilcSchedule schedule);
+void *silc_schedule_internal_init(SilcSchedule schedule, void *context);
 
 /* Uninitializes the platform specific scheduler context. */
 void silc_schedule_internal_uninit(void *context);
@@ -70,6 +69,8 @@ void silc_schedule_internal_signals_unblock(void *context);
 
 /* Internal task management routines. */
 
+static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
+                                          bool dispatch_all);
 static void silc_task_queue_alloc(SilcTaskQueue *queue);
 static void silc_task_queue_free(SilcTaskQueue queue);
 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd);
@@ -79,8 +80,6 @@ static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first);
 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
                                      SilcTaskPriority priority);
 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task);
-static int silc_schedule_task_timeout_compare(struct timeval *smaller, 
-                                             struct timeval *bigger);
 static void silc_task_del_by_context(SilcTaskQueue queue, void *context);
 static void silc_task_del_by_callback(SilcTaskQueue queue,
                                      SilcTaskCallback callback);
@@ -208,6 +207,7 @@ struct SilcTaskQueueStruct {
 
 */
 struct SilcScheduleStruct {
+  void *app_context;           /* Application specific context */
   SilcTaskQueue fd_queue;
   SilcTaskQueue timeout_queue;
   SilcTaskQueue generic_queue;
@@ -225,9 +225,10 @@ struct SilcScheduleStruct {
 /* Initializes the scheduler. This returns the scheduler context that
    is given as arugment usually to all silc_schedule_* functions.
    The `max_tasks' indicates the number of maximum tasks that the
-   scheduler can handle. */
+   scheduler can handle. The `app_context' is application specific
+   context that is delivered to task callbacks. */
 
-SilcSchedule silc_schedule_init(int max_tasks)
+SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
 {
   SilcSchedule schedule;
 
@@ -249,12 +250,13 @@ SilcSchedule silc_schedule_init(int max_tasks)
   schedule->max_fd = max_tasks;
   schedule->timeout = NULL;
   schedule->valid = TRUE;
+  schedule->app_context = app_context;
 
   /* Allocate scheduler lock */
   silc_mutex_alloc(&schedule->lock);
 
   /* Initialize the platform specific scheduler. */
-  schedule->internal = silc_schedule_internal_init(schedule);
+  schedule->internal = silc_schedule_internal_init(schedule, app_context);
 
   return schedule;
 }
@@ -271,6 +273,19 @@ bool silc_schedule_uninit(SilcSchedule schedule)
   if (schedule->valid == TRUE)
     return FALSE;
 
+  /* Dispatch all timeouts before going away */
+  SILC_SCHEDULE_LOCK(schedule);
+  silc_mutex_lock(schedule->timeout_queue->lock);
+  silc_schedule_dispatch_timeout(schedule, TRUE);
+  silc_mutex_unlock(schedule->timeout_queue->lock);
+  SILC_SCHEDULE_UNLOCK(schedule);
+
+  /* Deliver signals before going away */
+  if (schedule->signal_tasks) {
+    silc_schedule_internal_signals_call(schedule->internal, schedule);
+    schedule->signal_tasks = FALSE;
+  }
+
   /* Unregister all tasks */
   silc_schedule_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
   silc_schedule_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
@@ -287,6 +302,7 @@ bool silc_schedule_uninit(SilcSchedule schedule)
   silc_schedule_internal_uninit(schedule->internal);
 
   silc_mutex_free(schedule->lock);
+  silc_free(schedule);
 
   return TRUE;
 }
@@ -326,8 +342,8 @@ void silc_schedule_stop(SilcSchedule schedule)
 static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
 {
   SilcTask task;
-  int i, last_fd = schedule->last_fd;
-  SilcUInt32 fd;
+  int i;
+  SilcUInt32 fd, last_fd = schedule->last_fd;
 
   for (i = 0; i <= last_fd; i++) {
     if (schedule->fd_list[i].events == 0)
@@ -350,7 +366,8 @@ static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
        silc_mutex_unlock(schedule->fd_queue->lock);
        SILC_SCHEDULE_UNLOCK(schedule);
-       task->callback(schedule, SILC_TASK_READ, task->fd, task->context);
+       task->callback(schedule, schedule->app_context,
+                      SILC_TASK_READ, task->fd, task->context);
        SILC_SCHEDULE_LOCK(schedule);
        silc_mutex_lock(schedule->fd_queue->lock);
       }
@@ -359,7 +376,8 @@ static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
        silc_mutex_unlock(schedule->fd_queue->lock);
        SILC_SCHEDULE_UNLOCK(schedule);
-       task->callback(schedule, SILC_TASK_WRITE, task->fd, task->context);
+       task->callback(schedule, schedule->app_context,
+                      SILC_TASK_WRITE, task->fd, task->context);
        SILC_SCHEDULE_LOCK(schedule);
        silc_mutex_lock(schedule->fd_queue->lock);
       }
@@ -389,7 +407,8 @@ static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
        if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
          silc_mutex_unlock(schedule->generic_queue->lock);
          SILC_SCHEDULE_UNLOCK(schedule);
-         task->callback(schedule, SILC_TASK_READ, fd, task->context);
+         task->callback(schedule, schedule->app_context,
+                        SILC_TASK_READ, fd, task->context);
          SILC_SCHEDULE_LOCK(schedule);
          silc_mutex_lock(schedule->generic_queue->lock);
        }
@@ -398,7 +417,8 @@ static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
        if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
          silc_mutex_unlock(schedule->generic_queue->lock);
          SILC_SCHEDULE_UNLOCK(schedule);
-         task->callback(schedule, SILC_TASK_WRITE, fd, task->context);
+         task->callback(schedule, schedule->app_context,
+                        SILC_TASK_WRITE, fd, task->context);
          SILC_SCHEDULE_LOCK(schedule);
          silc_mutex_lock(schedule->generic_queue->lock);
        }
@@ -436,7 +456,8 @@ static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
    phase. */
 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
 
-static void silc_schedule_dispatch_timeout(SilcSchedule schedule)
+static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
+                                          bool dispatch_all)
 {
   SilcTaskQueue queue = schedule->timeout_queue;
   SilcTask task;
@@ -454,11 +475,13 @@ static void silc_schedule_dispatch_timeout(SilcSchedule schedule)
        the expired tasks. */
     while(1) {
       /* Execute the task if the timeout has expired */
-      if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
+      if (dispatch_all ||
+         silc_compare_timeval(&task->timeout, &curtime)) {
         if (task->valid) {
          silc_mutex_unlock(queue->lock);
          SILC_SCHEDULE_UNLOCK(schedule);
-         task->callback(schedule, SILC_TASK_EXPIRE, task->fd, task->context);
+         task->callback(schedule, schedule->app_context,
+                        SILC_TASK_EXPIRE, task->fd, task->context);
          SILC_SCHEDULE_LOCK(schedule);
          silc_mutex_lock(queue->lock);
        }
@@ -507,9 +530,9 @@ static void silc_schedule_select_timeout(SilcSchedule schedule)
     if (task && task->valid == TRUE) {
       /* If the timeout is in past, we will run the task and all other
         timeout tasks from the past. */
-      if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
-       silc_schedule_dispatch_timeout(schedule);
-                                               
+      if (silc_compare_timeval(&task->timeout, &curtime)) {
+       silc_schedule_dispatch_timeout(schedule, FALSE);
+
        /* The task(s) has expired and doesn't exist on the task queue
           anymore. We continue with new timeout. */
        queue = schedule->timeout_queue;
@@ -614,7 +637,7 @@ bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
   case 0:
     /* Timeout */
     silc_mutex_lock(schedule->timeout_queue->lock);
-    silc_schedule_dispatch_timeout(schedule);
+    silc_schedule_dispatch_timeout(schedule, FALSE);
     silc_mutex_unlock(schedule->timeout_queue->lock);
     break;
   default:
@@ -670,6 +693,16 @@ void silc_schedule_wakeup(SilcSchedule schedule)
 #endif
 }
 
+/* Returns the application specific context that was saved into the
+   scheduler in silc_schedule_init function.  The context is also
+   returned to application in task callback functions, but this function
+   may be used to get it as well if needed. */
+
+void *silc_schedule_get_context(SilcSchedule schedule)
+{
+  return schedule->app_context;
+}
+
 /* Add new task to the scheduler */
 
 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
@@ -682,6 +715,9 @@ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
   SilcTaskQueue queue;
   int timeout = FALSE;
 
+  if (!schedule->valid)
+    return NULL;
+
   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd, 
                  type, priority));
 
@@ -703,7 +739,7 @@ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
 
          /* Add the fd to be listened, the task found now applies to this
             fd as well. */
-         silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
+         silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
          return task;
        }
        
@@ -742,7 +778,7 @@ SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
   /* If the task is non-timeout task we have to tell the scheduler that we
      would like to have these tasks scheduled at some odd distant future. */
   if (type != SILC_TASK_TIMEOUT)
-    silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
+    silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
 
   silc_mutex_lock(queue->lock);
 
@@ -843,21 +879,28 @@ void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
    call this directly if wanted. This can be called multiple times for
    one file descriptor to set different iomasks. */
 
-void silc_schedule_set_listen_fd(SilcSchedule schedule,
-                                SilcUInt32 fd, SilcTaskEvent iomask)
+void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
+                                SilcTaskEvent mask, bool send_events)
 {
   int i;
   bool found = FALSE;
 
+  if (!schedule->valid)
+    return;
+
   SILC_SCHEDULE_LOCK(schedule);
 
   for (i = 0; i < schedule->max_fd; i++)
     if (schedule->fd_list[i].fd == fd) {
       schedule->fd_list[i].fd = fd;
-      schedule->fd_list[i].events = iomask;
+      schedule->fd_list[i].events = mask;
       if (i > schedule->last_fd)
        schedule->last_fd = i;
       found = TRUE;
+      if (send_events) {
+       schedule->fd_list[i].revents = mask;
+       silc_schedule_dispatch_nontimeout(schedule);
+      }
       break;
     }
 
@@ -865,9 +908,13 @@ void silc_schedule_set_listen_fd(SilcSchedule schedule,
     for (i = 0; i < schedule->max_fd; i++)
       if (schedule->fd_list[i].events == 0) {
        schedule->fd_list[i].fd = fd;
-       schedule->fd_list[i].events = iomask;
+       schedule->fd_list[i].events = mask;
        if (i > schedule->last_fd)
          schedule->last_fd = i;
+       if (send_events) {
+         schedule->fd_list[i].revents = mask;
+         silc_schedule_dispatch_nontimeout(schedule);
+       }
        break;
       }
 
@@ -936,6 +983,7 @@ static void silc_task_queue_alloc(SilcTaskQueue *queue)
 static void silc_task_queue_free(SilcTaskQueue queue)
 {
   silc_mutex_free(queue->lock);
+  memset(queue, 'F', sizeof(*queue));
   silc_free(queue);
 }
 
@@ -1035,7 +1083,7 @@ static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
     if (first == prev)
       break;
 
-    if (silc_schedule_task_timeout_compare(&prev->timeout, &task->timeout))
+    if (silc_compare_timeval(&prev->timeout, &task->timeout))
       task = prev;
 
     prev = prev->prev;
@@ -1068,13 +1116,11 @@ static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
 
       /* If we have longer timeout than with the task head of us
         we have found our spot. */
-      if (silc_schedule_task_timeout_compare(&prev->timeout, 
-                                            &newtask->timeout))
+      if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
        break;
 
       /* If we are equal size of timeout we will be after it. */
-      if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
-                                             &prev->timeout))
+      if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
        break;
 
       /* We have shorter timeout, compare to next one. */
@@ -1089,11 +1135,9 @@ static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
     
     if (prev == task) {
       /* Check if we are going to be the first task in the queue */
-      if (silc_schedule_task_timeout_compare(&prev->timeout, 
-                                            &newtask->timeout))
+      if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
        break;
-      if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
-                                             &prev->timeout))
+      if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
        break;
 
       /* We are now the first task in queue */
@@ -1107,13 +1151,11 @@ static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
 
       /* If we have longer timeout than with the task head of us
         we have found our spot. */
-      if (silc_schedule_task_timeout_compare(&prev->timeout, 
-                                            &newtask->timeout))
+      if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
        break;
 
       /* If we are equal size of timeout, priority kicks in place. */
-      if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
-                                             &prev->timeout))
+      if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
        if (prev->priority >= SILC_TASK_PRI_NORMAL)
          break;
 
@@ -1129,11 +1171,9 @@ static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
     
     if (prev == task) {
       /* Check if we are going to be the first task in the queue */
-      if (silc_schedule_task_timeout_compare(&prev->timeout, 
-                                            &newtask->timeout))
+      if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
        break;
-      if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
-                                             &prev->timeout))
+      if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
        if (prev->priority >= SILC_TASK_PRI_NORMAL)
          break;
 
@@ -1172,10 +1212,11 @@ static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
     next = first;
 
     while(1) {
-      next = next->next;
-      silc_free(next->prev);
-      if (next == first)
+      old = next->next;
+      silc_free(next);
+      if (old == first)
        break;
+      next = old;
     }
 
     queue->task = NULL;
@@ -1211,20 +1252,6 @@ static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
   }
 }
 
-/* Compare two time values. If the first argument is smaller than the
-   second this function returns TRUE. */
-
-static int silc_schedule_task_timeout_compare(struct timeval *smaller, 
-                                             struct timeval *bigger)
-{
-  if ((smaller->tv_sec < bigger->tv_sec) ||
-      ((smaller->tv_sec == bigger->tv_sec) &&
-       (smaller->tv_usec < bigger->tv_usec)))
-    return TRUE;
-
-  return FALSE;
-}
-
 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
 {
   SilcTask next;