updates.
[silc.git] / lib / silcutil / silcschedule.c
index 884091838213d1b0f92dbf7600b0fcb66e33780a..db939c3ad201df748aa4f7fde12362d681245dde 100644 (file)
 /* Forward declarations */
 typedef struct SilcTaskQueueStruct *SilcTaskQueue;
 
-/* System specific routines. Implemented under unix/ and win32/. */
+/* System specific routines. Implemented under unix/, win32/ and such. */
 
 /* System specific select(). Returns same values as normal select(). */
-int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count, struct timeval *timeout);
+int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count, 
+               struct timeval *timeout);
 
-/* Initializes the wakeup of the scheduler. In multi-threaded environment
+/* Initializes the platform specific scheduler.  This for example initializes
+   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. This will initialize the wakeup for the scheduler.
-   Any tasks that needs to be registered must be registered to the `queue'.
-   It is guaranteed that the scheduler will automatically free any
-   registered tasks in this queue. This is system specific routine. */
-void *silc_schedule_wakeup_init(SilcSchedule schedule);
+   the task queues.  Returns context to the platform specific scheduler. */
+void *silc_schedule_internal_init(SilcSchedule schedule);
 
-/* Uninitializes the system specific wakeup. */
-void silc_schedule_wakeup_uninit(void *context);
+/* Uninitializes the platform specific scheduler context. */
+void silc_schedule_internal_uninit(void *context);
 
 /* Wakes up the scheduler. This is platform specific routine */
-void silc_schedule_wakeup_internal(void *context);
+void silc_schedule_internal_wakeup(void *context);
 
+/* Register signal */
+void silc_schedule_internal_signal_register(void *context,
+                                            SilcUInt32 signal,
+                                            SilcTaskCallback callback,
+                                            void *callback_context);
+
+/* Unregister signal */
+void silc_schedule_internal_signal_unregister(void *context,
+                                              SilcUInt32 signal,
+                                              SilcTaskCallback callback,
+                                              void *callback_context);
+
+/* Mark signal to be called later. */
+void silc_schedule_internal_signal_call(void *context, SilcUInt32 signal);
+
+/* Call all signals */
+void silc_schedule_internal_signals_call(void *context,
+                                        SilcSchedule schedule);
+
+/* Block registered signals in scheduler. */
+void silc_schedule_internal_signals_block(void *context);
+
+/* Unblock registered signals in schedule. */
+void silc_schedule_internal_signals_unblock(void *context);
 
 /* Internal task management routines. */
 
@@ -64,20 +87,34 @@ static void silc_task_del_by_callback(SilcTaskQueue queue,
 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd);
 
 /* Returns the task queue by task type */
-#define SILC_SCHEDULE_GET_QUEUE(type)                                  \
-  (type == SILC_TASK_FD ? schedule->fd_queue :                         \
-   type == SILC_TASK_TIMEOUT ? schedule->timeout_queue :               \
+#define SILC_SCHEDULE_GET_QUEUE(type)                          \
+  (type == SILC_TASK_FD ? schedule->fd_queue :                 \
+   type == SILC_TASK_TIMEOUT ? schedule->timeout_queue :       \
    schedule->generic_queue)
 
+/* Locks. These also blocks signals that we care about and thus guarantee
+   that while we are in scheduler no signals can happen.  This way we can
+   synchronise signals with SILC Scheduler. */
+#define SILC_SCHEDULE_LOCK(schedule)                           \
+do {                                                           \
+  silc_schedule_internal_signals_block(schedule->internal);    \
+  silc_mutex_lock(schedule->lock);                             \
+} while (0)
+#define SILC_SCHEDULE_UNLOCK(schedule)                         \
+do {                                                           \
+  silc_mutex_unlock(schedule->lock);                           \
+  silc_schedule_internal_signals_unblock(schedule->internal);  \
+} while (0)
+
 /* SILC Task object. Represents one task in the scheduler. */
 struct SilcTaskStruct {
   SilcUInt32 fd;
-  struct timeval timeout;
-  SilcTaskCallback callback;
-  void *context;
-  bool valid;
-  SilcTaskPriority priority;
-  SilcTaskType type;
+  SilcTaskCallback callback;      /* Task callback */
+  void *context;                  /* Task callback context */
+  struct timeval timeout;         /* Set for timeout tasks */
+  unsigned int valid : 1;         /* Set when task is valid */
+  unsigned int priority : 2;      /* Priority of the task */
+  unsigned int type : 5;           /* Type of the task */
 
   /* Pointers forming doubly linked circular list */
   struct SilcTaskStruct *next;
@@ -156,16 +193,19 @@ struct SilcTaskQueueStruct {
        File descriptor sets for select(). These are automatically managed
        by the scheduler and should not be touched otherwise.
 
-   void *wakeup
+   void *internal
 
-       System specific wakeup context. On multi-threaded environments the
-       scheduler needs to be wakenup (in the thread) when tasks are added
-       or removed. This is initialized by silc_schedule_wakeup_init.
+       System specific scheduler context.
 
    SILC_MUTEX_DEFINE(lock)
   
        Scheduler lock.
 
+   bool signal_tasks
+
+       TRUE when tasks has been registered from signals.  Next round in
+       scheduler will call the callbacks when this is TRUE.
+
 */
 struct SilcScheduleStruct {
   SilcTaskQueue fd_queue;
@@ -176,9 +216,10 @@ struct SilcScheduleStruct {
   SilcUInt32 last_fd;
   struct timeval *timeout;
   bool valid;
-  void *wakeup;
+  void *internal;
   SILC_MUTEX_DEFINE(lock);
   bool is_locked;
+  bool signal_tasks;
 };
 
 /* Initializes the scheduler. This returns the scheduler context that
@@ -212,8 +253,8 @@ SilcSchedule silc_schedule_init(int max_tasks)
   /* Allocate scheduler lock */
   silc_mutex_alloc(&schedule->lock);
 
-  /* Initialize the wakeup, for multi-threads support */
-  schedule->wakeup = silc_schedule_wakeup_init(schedule);
+  /* Initialize the platform specific scheduler. */
+  schedule->internal = silc_schedule_internal_init(schedule);
 
   return schedule;
 }
@@ -242,8 +283,8 @@ bool silc_schedule_uninit(SilcSchedule schedule)
 
   silc_free(schedule->fd_list);
 
-  /* Uninit the wakeup */
-  silc_schedule_wakeup_uninit(schedule->wakeup);
+  /* Uninit the platform specific scheduler. */
+  silc_schedule_internal_uninit(schedule->internal);
 
   silc_mutex_free(schedule->lock);
 
@@ -254,13 +295,13 @@ bool silc_schedule_uninit(SilcSchedule schedule)
 
 bool silc_schedule_reinit(SilcSchedule schedule, int max_tasks)
 {
-  silc_mutex_lock(schedule->lock);
+  SILC_SCHEDULE_LOCK(schedule);
   if (schedule->max_fd <= max_tasks)
     return FALSE;
   schedule->fd_list = silc_realloc(schedule->fd_list, 
                                   (sizeof(*schedule->fd_list) * max_tasks));
   schedule->max_fd = max_tasks;
-  silc_mutex_unlock(schedule->lock);
+  SILC_SCHEDULE_UNLOCK(schedule);
   return TRUE;
 }
 
@@ -271,7 +312,9 @@ bool silc_schedule_reinit(SilcSchedule schedule, int max_tasks)
 void silc_schedule_stop(SilcSchedule schedule)
 {
   SILC_LOG_DEBUG(("Stopping scheduler"));
+  SILC_SCHEDULE_LOCK(schedule);
   schedule->valid = FALSE;
+  SILC_SCHEDULE_UNLOCK(schedule);
 }
 
 /* Executes nontimeout tasks. It then checks whether any of ther fd tasks
@@ -306,18 +349,18 @@ static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
       /* Is the task ready for reading */
       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
        silc_mutex_unlock(schedule->fd_queue->lock);
-       silc_mutex_unlock(schedule->lock);
+       SILC_SCHEDULE_UNLOCK(schedule);
        task->callback(schedule, SILC_TASK_READ, task->fd, task->context);
-       silc_mutex_lock(schedule->lock);
+       SILC_SCHEDULE_LOCK(schedule);
        silc_mutex_lock(schedule->fd_queue->lock);
       }
 
       /* Is the task ready for writing */
       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
        silc_mutex_unlock(schedule->fd_queue->lock);
-       silc_mutex_unlock(schedule->lock);
+       SILC_SCHEDULE_UNLOCK(schedule);
        task->callback(schedule, SILC_TASK_WRITE, task->fd, task->context);
-       silc_mutex_lock(schedule->lock);
+       SILC_SCHEDULE_LOCK(schedule);
        silc_mutex_lock(schedule->fd_queue->lock);
       }
 
@@ -345,18 +388,18 @@ static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
        /* Is the task ready for reading */                             
        if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
          silc_mutex_unlock(schedule->generic_queue->lock);
-         silc_mutex_unlock(schedule->lock);
+         SILC_SCHEDULE_UNLOCK(schedule);
          task->callback(schedule, SILC_TASK_READ, fd, task->context);
-         silc_mutex_lock(schedule->lock);
+         SILC_SCHEDULE_LOCK(schedule);
          silc_mutex_lock(schedule->generic_queue->lock);
        }
 
        /* Is the task ready for writing */                             
        if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
          silc_mutex_unlock(schedule->generic_queue->lock);
-         silc_mutex_unlock(schedule->lock);
+         SILC_SCHEDULE_UNLOCK(schedule);
          task->callback(schedule, SILC_TASK_WRITE, fd, task->context);
-         silc_mutex_lock(schedule->lock);
+         SILC_SCHEDULE_LOCK(schedule);
          silc_mutex_lock(schedule->generic_queue->lock);
        }
 
@@ -414,9 +457,9 @@ static void silc_schedule_dispatch_timeout(SilcSchedule schedule)
       if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
         if (task->valid) {
          silc_mutex_unlock(queue->lock);
-         silc_mutex_unlock(schedule->lock);
+         SILC_SCHEDULE_UNLOCK(schedule);
          task->callback(schedule, SILC_TASK_EXPIRE, task->fd, task->context);
-         silc_mutex_lock(schedule->lock);
+         SILC_SCHEDULE_LOCK(schedule);
          silc_mutex_lock(queue->lock);
        }
 
@@ -518,7 +561,15 @@ bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
   SILC_LOG_DEBUG(("In scheduler loop"));
 
   if (!schedule->is_locked)
-    silc_mutex_lock(schedule->lock);
+    SILC_SCHEDULE_LOCK(schedule);
+
+  /* Deliver signals if any has been set to be called */
+  if (schedule->signal_tasks) {
+    SILC_SCHEDULE_UNLOCK(schedule);
+    silc_schedule_internal_signals_call(schedule->internal, schedule);
+    schedule->signal_tasks = FALSE;
+    SILC_SCHEDULE_LOCK(schedule);
+  }
 
   /* If the task queues aren't initialized or we aren't valid anymore
      we will return */
@@ -526,7 +577,7 @@ bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
        && !schedule->generic_queue) || schedule->valid == FALSE) {
     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
     if (!schedule->is_locked)
-      silc_mutex_unlock(schedule->lock);
+      SILC_SCHEDULE_UNLOCK(schedule);
     return FALSE;
   }
 
@@ -542,7 +593,7 @@ bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
     schedule->timeout = &timeout;
   }
 
-  silc_mutex_unlock(schedule->lock);
+  SILC_SCHEDULE_UNLOCK(schedule);
 
   /* This is the main select(). The program blocks here until some
      of the selected file descriptors change status or the selected
@@ -551,7 +602,7 @@ bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
   ret = silc_select(schedule->fd_list, schedule->last_fd + 1, 
                    schedule->timeout);
 
-  silc_mutex_lock(schedule->lock);
+  SILC_SCHEDULE_LOCK(schedule);
 
   switch (ret) {
   case -1:
@@ -574,7 +625,7 @@ bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
   }
 
   if (!schedule->is_locked)
-    silc_mutex_unlock(schedule->lock);
+    SILC_SCHEDULE_UNLOCK(schedule);
 
   return TRUE;
 }
@@ -592,14 +643,14 @@ void silc_schedule(SilcSchedule schedule)
     return;
   }
 
-  silc_mutex_lock(schedule->lock);
+  SILC_SCHEDULE_LOCK(schedule);
   schedule->is_locked = TRUE;
 
   /* Start the scheduler loop */
   while (silc_schedule_one(schedule, -1)) 
     ;
 
-  silc_mutex_unlock(schedule->lock);
+  SILC_SCHEDULE_UNLOCK(schedule);
 }
 
 /* Wakes up the scheduler. This is used only in multi-threaded
@@ -613,9 +664,9 @@ void silc_schedule_wakeup(SilcSchedule schedule)
 {
 #ifdef SILC_THREADS
   SILC_LOG_DEBUG(("Wakeup scheduler"));
-  silc_mutex_lock(schedule->lock);
-  silc_schedule_wakeup_internal(schedule->wakeup);
-  silc_mutex_unlock(schedule->lock);
+  SILC_SCHEDULE_LOCK(schedule);
+  silc_schedule_internal_wakeup(schedule->internal);
+  SILC_SCHEDULE_UNLOCK(schedule);
 #endif
 }
 
@@ -798,7 +849,7 @@ void silc_schedule_set_listen_fd(SilcSchedule schedule,
   int i;
   bool found = FALSE;
 
-  silc_mutex_lock(schedule->lock);
+  SILC_SCHEDULE_LOCK(schedule);
 
   for (i = 0; i < schedule->max_fd; i++)
     if (schedule->fd_list[i].fd == fd) {
@@ -820,7 +871,7 @@ void silc_schedule_set_listen_fd(SilcSchedule schedule,
        break;
       }
 
-  silc_mutex_unlock(schedule->lock);
+  SILC_SCHEDULE_UNLOCK(schedule);
 }
 
 /* Removes a file descriptor from listen list. */
@@ -829,7 +880,7 @@ void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
 {
   int i;
 
-  silc_mutex_lock(schedule->lock);
+  SILC_SCHEDULE_LOCK(schedule);
 
   SILC_LOG_DEBUG(("Unset listen fd %d", fd));
 
@@ -842,7 +893,34 @@ void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
       break;
     }
 
-  silc_mutex_unlock(schedule->lock);
+  SILC_SCHEDULE_UNLOCK(schedule);
+}
+
+/* Register a new signal */
+
+void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
+                                  SilcTaskCallback callback, void *context)
+{
+  silc_schedule_internal_signal_register(schedule->internal, signal,
+                                        callback, context);
+}
+
+/* Unregister a new signal */
+
+void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
+                                    SilcTaskCallback callback, void *context)
+{
+  silc_schedule_internal_signal_unregister(schedule->internal, signal,
+                                          callback, context);
+}
+
+/* Call signal indicated by `signal'. */
+
+void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
+{
+  /* Mark that signals needs to be delivered later. */
+  silc_schedule_internal_signal_call(schedule->internal, signal);
+  schedule->signal_tasks = TRUE;
 }
 
 /* Allocates a newtask task queue into the scheduler */
@@ -858,6 +936,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);
 }
 
@@ -1094,10 +1173,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;