Added asynchronous event tasks to SILC Scheduler. Added
[crypto.git] / lib / silcutil / silcfsm.c
index 863516dadd9ac53a40a97ac11ba98d52741a264e..2ded983253aa77906cd439210b69306afe92116c 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2005 - 2006 Pekka Riikonen
+  Copyright (C) 2005 - 2007 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
 #include "silc.h"
 
 SILC_TASK_CALLBACK(silc_fsm_run);
-SILC_TASK_CALLBACK(silc_fsm_finish);
-SILC_TASK_CALLBACK(silc_fsm_sema_timedout);
+SILC_TASK_CALLBACK(silc_fsm_finish_fsm);
+SILC_TASK_CALLBACK(silc_fsm_event_timedout);
 SILC_TASK_CALLBACK(silc_fsm_start_real_thread);
-static void *silc_fsm_thread(void *context);
-static void silc_fsm_thread_termination_post(SilcFSMSema sema);
-static void silc_fsm_sema_ref(SilcFSMSema sema);
-static void silc_fsm_sema_unref(SilcFSMSema sema);
+static void silc_fsm_thread_termination_signal(SilcFSMEvent event);
+static void silc_fsm_event_ref(SilcFSMEvent event);
+static void silc_fsm_event_unref(SilcFSMEvent event);
+void *silc_fsm_thread(void *context);
 
 /* Allocate FSM */
 
@@ -38,11 +38,11 @@ SilcFSM silc_fsm_alloc(void *fsm_context,
   SilcFSM fsm;
 
   fsm = silc_calloc(1, sizeof(*fsm));
-  if (!fsm)
+  if (silc_unlikely(!fsm))
     return NULL;
 
-  if (!silc_fsm_init(fsm, fsm_context, destructor,
-                    destructor_context, schedule)) {
+  if (silc_unlikely(!silc_fsm_init(fsm, fsm_context, destructor,
+                                  destructor_context, schedule))) {
     silc_free(fsm);
     return NULL;
   }
@@ -58,8 +58,13 @@ SilcBool silc_fsm_init(SilcFSM fsm,
                       void *destructor_context,
                       SilcSchedule schedule)
 {
-  if (!schedule)
-    return FALSE;
+  if (!schedule) {
+    schedule = silc_schedule_get_global();
+    if (!schedule) {
+      silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
+      return FALSE;
+    }
+  }
 
   fsm->fsm_context = fsm_context;
   fsm->state_context = NULL;
@@ -68,8 +73,9 @@ SilcBool silc_fsm_init(SilcFSM fsm,
   fsm->schedule = schedule;
   fsm->thread = FALSE;
   fsm->async_call = FALSE;
-  fsm->u.m.threads = 0;
+  fsm->started = FALSE;
   fsm->u.m.lock = NULL;
+  silc_atomic_init32(&fsm->u.m.threads, 0);
 
   return TRUE;
 }
@@ -85,7 +91,7 @@ SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
   SilcFSMThread thread;
 
   thread = silc_calloc(1, sizeof(*thread));
-  if (!thread)
+  if (silc_unlikely(!thread))
     return NULL;
 
   silc_fsm_thread_init(thread, fsm, thread_context, destructor,
@@ -105,9 +111,7 @@ void silc_fsm_thread_init(SilcFSMThread thread,
   SILC_LOG_DEBUG(("Initializing new thread %p (%s)",
                  thread, real_thread ? "real" : "FSM"));
 
-#if defined(SILC_DEBUG)
-  assert(!fsm->thread);
-#endif /* SILC_DEBUG */
+  SILC_VERIFY(!fsm->thread);
 
   thread->fsm_context = thread_context;
   thread->state_context = NULL;
@@ -116,11 +120,12 @@ void silc_fsm_thread_init(SilcFSMThread thread,
   thread->schedule = fsm->schedule;
   thread->thread = TRUE;
   thread->async_call = FALSE;
+  thread->started = FALSE;
   thread->real_thread = real_thread;
   thread->u.t.fsm = fsm;
 
   /* Add to machine */
-  fsm->u.m.threads++;
+  silc_atomic_add_int32(&fsm->u.m.threads, 1);
 
   /* Allocate lock for the machine if using real threads. */
   if (real_thread && !fsm->u.m.lock)
@@ -138,18 +143,21 @@ SILC_TASK_CALLBACK(silc_fsm_free_final)
 
 #if defined(SILC_DEBUG)
   /* We must be finished */
-  assert(f->finished);
+  SILC_ASSERT(f->finished);
 
   /* Machine must not have active threads */
-  if (!f->thread && f->u.m.threads)
-    assert(f->u.m.threads == 0);
+  if (!f->thread && silc_atomic_get_int32(&f->u.m.threads))
+    SILC_ASSERT(silc_atomic_get_int32(&f->u.m.threads) == 0);
 #endif /* SILC_DEBUG */
 
   if (!f->thread && f->u.m.lock)
     silc_mutex_free(f->u.m.lock);
 
-  if (f->thread && f->u.t.sema)
-    silc_fsm_sema_free(f->u.t.sema);
+  if (f->thread && f->u.t.event)
+    silc_fsm_event_free(f->u.t.event);
+
+  if (!f->thread)
+    silc_atomic_uninit32(&f->u.m.threads);
 
   silc_free(f);
 }
@@ -159,7 +167,12 @@ SILC_TASK_CALLBACK(silc_fsm_free_final)
 void silc_fsm_free(void *fsm)
 {
   SilcFSM f = fsm;
-  silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final, f, 0, 1);
+  if (!f->thread)
+    if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final,
+                                      f, 0, 0))
+      return;
+  silc_fsm_free_final(f->schedule, silc_schedule_get_context(f->schedule),
+                     0, 0, f);
 }
 
 /* Task to start real thread. We start threads through scheduler, not
@@ -192,16 +205,28 @@ void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
   f->finished = FALSE;
   f->next_state = start_state;
   f->synchronous = FALSE;
+  f->started = TRUE;
 
   /* Start real thread through scheduler */
   if (f->thread && f->real_thread) {
-    silc_schedule_task_add_timeout(f->schedule, silc_fsm_start_real_thread,
-                                  f, 0, 1);
+    if (!silc_schedule_task_add_timeout(f->schedule,
+                                       silc_fsm_start_real_thread,
+                                       f, 0, 0))
+      silc_fsm_start_real_thread(f->schedule,
+                                silc_schedule_get_context(f->schedule),
+                                0, 0, f);
+    silc_schedule_wakeup(f->schedule);
     return;
   }
 
   /* Normal FSM operation */
-  silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
+  if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
+    silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
+
+  /* Wakeup scheduler in case we are starting this thread from another
+     real thread. */
+  if (f->thread)
+    silc_schedule_wakeup(f->schedule);
 }
 
 /* Start FSM in the specified state synchronously */
@@ -215,6 +240,7 @@ void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
   f->finished = FALSE;
   f->next_state = start_state;
   f->synchronous = TRUE;
+  f->started = TRUE;
 
   /* Start real thread directly */
   if (f->thread && f->real_thread) {
@@ -242,11 +268,18 @@ void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
                         SilcUInt32 seconds, SilcUInt32 useconds)
 {
   SilcFSM f = fsm;
+
   f->next_state = next_state;
   if (!seconds && !useconds)
     return;
+
   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
                                 seconds, useconds);
+  f->next_later = TRUE;
+
+  /* Wakeup up the scheduler just in case this was called from another
+     thread. */
+  silc_schedule_wakeup(f->schedule);
 }
 
 /* Continue after callback or async operation */
@@ -254,7 +287,19 @@ void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
 void silc_fsm_continue(void *fsm)
 {
   SilcFSM f = fsm;
-  silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
+
+  if (f->next_later) {
+    /* Cancel next_later timeout */
+    silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
+    f->next_later = FALSE;
+  }
+
+  if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
+    silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
+
+  /* Wakeup up the scheduler just in case this was called from another
+     thread. */
+  silc_schedule_wakeup(f->schedule);
 }
 
 /* Continue after callback or async operation immediately */
@@ -262,9 +307,46 @@ void silc_fsm_continue(void *fsm)
 void silc_fsm_continue_sync(void *fsm)
 {
   SilcFSM f = fsm;
+  if (f->next_later) {
+    silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
+    f->next_later = FALSE;
+  }
   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
 }
 
+/* Finish FSM */
+
+void silc_fsm_finish(void *fsm)
+{
+  SilcFSM f = fsm;
+
+  SILC_VERIFY(!f->finished);
+
+  f->started = FALSE;
+  f->finished = TRUE;
+
+  silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
+  f->next_later = FALSE;
+
+  /* If we are thread and using real threads, the FSM thread will finish
+     after the real thread has finished, in the main thread. */
+  if (f->thread && f->real_thread) {
+    /* Stop the real thread's scheduler to finish the thread */
+    silc_schedule_stop(f->schedule);
+    silc_schedule_wakeup(f->schedule);
+    return;
+  }
+
+  /* Normal FSM operation */
+  if (!f->synchronous)
+    if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm,
+                                      f, 0, 0))
+      return;
+
+  silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
+                     0, 0, fsm);
+}
+
 /* Return associated scheduler */
 
 SilcSchedule silc_fsm_get_schedule(void *fsm)
@@ -277,10 +359,18 @@ SilcSchedule silc_fsm_get_schedule(void *fsm)
 
 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
 {
-  assert(thread->thread);
+  SILC_VERIFY(thread->thread);
   return (SilcFSM)thread->u.t.fsm;
 }
 
+/* Returns TRUE if FSM is started */
+
+SilcBool silc_fsm_is_started(void *fsm)
+{
+  SilcFSM f = fsm;
+  return f->started;
+}
+
 /* Set context */
 
 void silc_fsm_set_context(void *fsm, void *fsm_context)
@@ -318,13 +408,15 @@ void *silc_fsm_get_state_context(void *fsm)
 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
 {
   SilcFSM t = thread;
-#if defined(SILC_DEBUG)
-  assert(t->thread);
-#endif /* SILC_DEBUG */
-  t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
-  if (!t->u.t.sema)
+
+  SILC_VERIFY(t->thread);
+
+  t->u.t.event = silc_fsm_event_alloc(t->u.t.fsm);
+  if (!t->u.t.event)
     return FALSE;
-  silc_fsm_sema_wait(t->u.t.sema, fsm);
+
+  SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
+  silc_fsm_event_wait(t->u.t.event, fsm);
   return TRUE;
 }
 
@@ -340,38 +432,24 @@ SILC_TASK_CALLBACK(silc_fsm_run)
   /* Run the states */
   do
     status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
-  while (status == SILC_FSM_CONTINUE);
+  while (status == SILC_FSM_ST_CONTINUE);
 
   switch (status) {
-  case SILC_FSM_WAIT:
+  case SILC_FSM_ST_YIELD:
+    /* Continue through scheduler */
+    silc_fsm_continue(fsm);
+    break;
+
+  case SILC_FSM_ST_WAIT:
     /* The machine is in hold */
     SILC_LOG_DEBUG(("State wait %p", fsm));
     fsm->synchronous = FALSE;
     break;
 
-  case SILC_FSM_FINISH:
+  case SILC_FSM_ST_FINISH:
     /* Finish the state machine */
     SILC_LOG_DEBUG(("State finish %p", fsm));
-#if defined(SILC_DEBUG)
-    assert(!fsm->finished);
-#endif /* SILC_DEBUG */
-    fsm->finished = TRUE;
-
-    /* If we are thread and using real threads, the FSM thread will finish
-       in the main thread, not in the created thread. */
-    if (fsm->thread && fsm->real_thread) {
-      silc_schedule_task_add_timeout(app_context, silc_fsm_finish, fsm, 0, 1);
-      silc_schedule_wakeup(app_context);
-      silc_schedule_stop(fsm->schedule);
-      break;
-    }
-
-    /* Normal FSM operation */
-    if (fsm->synchronous)
-      silc_fsm_finish(fsm->schedule, app_context, 0, 0, fsm);
-    else
-      silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish,
-                                    fsm, 0, 1);
+    silc_fsm_finish(fsm);
     break;
 
   default:
@@ -382,7 +460,7 @@ SILC_TASK_CALLBACK(silc_fsm_run)
 /* Finishes the FSM.  This is always executed in the main thread, even
    for FSM threads that were run in real threads. */
 
-SILC_TASK_CALLBACK(silc_fsm_finish)
+SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
 {
   SilcFSM fsm = context;
 
@@ -392,14 +470,14 @@ SILC_TASK_CALLBACK(silc_fsm_finish)
 
   if (fsm->thread) {
     /* This is thread, send signal */
-    if (fsm->u.t.sema) {
-      silc_fsm_thread_termination_post(fsm->u.t.sema);
-      silc_fsm_sema_free(fsm->u.t.sema);
-      fsm->u.t.sema = NULL;
+    if (fsm->u.t.event) {
+      silc_fsm_thread_termination_signal(fsm->u.t.event);
+      silc_fsm_event_free(fsm->u.t.event);
+      fsm->u.t.event = NULL;
     }
 
     /* Remove the thread from machine */
-    fsm->u.t.fsm->u.m.threads--;
+    silc_atomic_sub_int32(&fsm->u.t.fsm->u.m.threads, 1);
 
     /* Call the destructor callback only if the underlaying machine is
        still valid. */
@@ -407,6 +485,9 @@ SILC_TASK_CALLBACK(silc_fsm_finish)
       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
 
   } else {
+    /* Machine must not have active threads */
+    SILC_VERIFY(silc_atomic_get_int32(&fsm->u.m.threads) == 0);
+
     if (fsm->u.m.lock) {
       silc_mutex_free(fsm->u.m.lock);
       fsm->u.m.lock = NULL;
@@ -418,113 +499,113 @@ SILC_TASK_CALLBACK(silc_fsm_finish)
   }
 }
 
-/* Allocate FSM semaphore */
+/* Allocate FSM event */
 
-SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
+SilcFSMEvent silc_fsm_event_alloc(SilcFSM fsm)
 {
-  SilcFSMSema sema;
+  SilcFSMEvent event;
 
-  sema = silc_calloc(1, sizeof(*sema));
-  if (!sema)
+  event = silc_calloc(1, sizeof(*event));
+  if (silc_unlikely(!event))
     return NULL;
 
-  silc_fsm_sema_init(sema, fsm, value);
-  sema->allocated = TRUE;
+  silc_fsm_event_init(event, fsm);
+  event->allocated = TRUE;
 
-  return sema;
+  return event;
 }
 
-/* Initializes FSM semaphore */
+/* Initializes FSM event */
 
-void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
+void silc_fsm_event_init(SilcFSMEvent event, SilcFSM fsm)
 {
-  SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
-#if defined(SILC_DEBUG)
-  assert(!fsm->thread);
-#endif /* SILC_DEBUG */
-  memset(sema, 0, sizeof(*sema));
-  sema->fsm = fsm;
-  sema->refcnt = 0;
-  silc_list_init(sema->waiters, struct SilcFSMObject, next);
-  sema->value = value;
+  SILC_LOG_DEBUG(("Initializing event %p", event));
+  SILC_VERIFY(!fsm->thread);
+  memset(event, 0, sizeof(*event));
+  event->fsm = fsm;
+  event->refcnt = 0;
+  silc_list_init(event->waiters, struct SilcFSMObject, next);
 }
 
-/* Free semaphore */
+/* Free event */
 
-void silc_fsm_sema_free(SilcFSMSema sema)
+void silc_fsm_event_free(SilcFSMEvent event)
 {
-  if (sema->refcnt > 0)
+  if (event->refcnt > 0)
     return;
-#if defined(SILC_DEBUG)
-  assert(silc_list_count(sema->waiters) == 0);
-#endif /* SILC_DEBUG */
-  silc_free(sema);
+  if (silc_list_count(event->waiters) > 0)
+    return;
+  silc_free(event);
 }
 
-/* Reference semaphore */
+/* Reference event */
 
-static void silc_fsm_sema_ref(SilcFSMSema sema)
+static void silc_fsm_event_ref(SilcFSMEvent event)
 {
-  sema->refcnt++;
+  event->refcnt++;
 }
 
-/* Unreference semaphore */
+/* Unreference event */
 
-static void silc_fsm_sema_unref(SilcFSMSema sema)
+static void silc_fsm_event_unref(SilcFSMEvent event)
 {
-  sema->refcnt--;
-  if (sema->refcnt == 0 && sema->allocated)
-    silc_fsm_sema_free(sema);
+  event->refcnt--;
+  if (event->refcnt == 0 && event->allocated)
+    silc_fsm_event_free(event);
 }
 
-/* Wait until semaphore is non-zero. */
+/* Wait until event is non-zero. */
 
-SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
+SilcUInt32 silc_fsm_event_wait(SilcFSMEvent event, void *fsm)
 {
-  SilcMutex lock = sema->fsm->u.m.lock;
+  SilcMutex lock = event->fsm->u.m.lock;
 
   silc_mutex_lock(lock);
 
-  if (!sema->value) {
+  if (!event->value) {
 #if defined(SILC_DEBUG)
     SilcFSM entry;
-    silc_list_start(sema->waiters);
-    while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
-      assert(entry != fsm);
+    silc_list_start(event->waiters);
+    while ((entry = silc_list_get(event->waiters)))
+      SILC_ASSERT(entry != fsm);
 #endif /* SILC_DEBUG */
 
-    SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
+    SILC_LOG_DEBUG(("Waiting for event %p", event));
 
     /* Add the FSM to waiter list */
-    silc_list_add(sema->waiters, fsm);
+    silc_list_add(event->waiters, fsm);
     silc_mutex_unlock(lock);
     return 0;
   }
 
-  SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
+  SILC_LOG_DEBUG(("Received event %p", event));
+
+  /* Remove from waiting */
+  silc_list_del(event->waiters, fsm);
+
+  /* Decrease the counter only after all waiters have acquired the signal. */
+  if (!silc_list_count(event->waiters))
+    event->value--;
 
-  /* It is possible that this FSM is in the list so remove it */
-  silc_list_del(sema->waiters, fsm);
-  sema->value--;
   silc_mutex_unlock(lock);
   return 1;
 }
 
-/* Wait util semaphore is non-zero, or timeout occurs. */
+/* Wait util event is non-zero, or timeout occurs. */
 
-SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
-                                  SilcUInt32 seconds, SilcUInt32 useconds,
-                                  SilcBool *ret_to)
+SilcUInt32 silc_fsm_event_timedwait(SilcFSMEvent event, void *fsm,
+                                   SilcUInt32 seconds, SilcUInt32 useconds,
+                                   SilcBool *ret_to)
 {
-  SilcMutex lock = sema->fsm->u.m.lock;
+  SilcMutex lock = event->fsm->u.m.lock;
   SilcFSM f = fsm;
   SilcUInt32 value;
 
   silc_mutex_lock(lock);
 
-  if (f->sema_timedout) {
-    SILC_LOG_DEBUG(("Semaphore was timedout"));
-    f->sema_timedout = FALSE;
+  if (f->event_timedout) {
+    SILC_LOG_DEBUG(("Event waiting timedout"));
+    f->event_timedout = FALSE;
     if (ret_to)
       *ret_to = TRUE;
     silc_mutex_unlock(lock);
@@ -533,11 +614,11 @@ SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
 
   silc_mutex_unlock(lock);
 
-  value = silc_fsm_sema_wait(sema, fsm);
+  value = silc_fsm_event_wait(event, fsm);
   if (!value) {
-    silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
+    silc_schedule_task_add_timeout(f->schedule, silc_fsm_event_timedout,
                                   f, seconds, useconds);
-    f->sema = sema;
+    f->event = event;
   }
 
   if (ret_to)
@@ -546,44 +627,61 @@ SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
   return value;
 }
 
-/* Semaphore timedout */
+/* Event timedout */
 
-SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
+SILC_TASK_CALLBACK(silc_fsm_event_timedout)
 {
   SilcFSM fsm = context;
-  SilcMutex lock = fsm->sema->fsm->u.m.lock;
+  SilcMutex lock = fsm->event->fsm->u.m.lock;
 
-  SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
+  SILC_LOG_DEBUG(("Event %p timedout", fsm->event));
 
-  /* Remove the waiter from the semaphore */
+  /* Remove the waiter from the event waiters list */
   silc_mutex_lock(lock);
-  silc_list_del(fsm->sema->waiters, fsm);
+  silc_list_del(fsm->event->waiters, fsm);
 
   /* Continue */
-  if (fsm->sema) {
+  if (fsm->event) {
     silc_fsm_continue(fsm);
-    fsm->sema_timedout = TRUE;
-    fsm->sema = NULL;
+    fsm->event_timedout = TRUE;
+    fsm->event = NULL;
   }
 
   silc_mutex_unlock(lock);
 }
 
-/* Signalled, semaphore */
+/* Signalled, event */
 
 SILC_TASK_CALLBACK(silc_fsm_signal)
 {
-  SilcFSMSemaPost p = context;
-  SilcMutex lock = p->sema->fsm->u.m.lock;
+  SilcFSMEventSignal p = context;
+  SilcMutex lock = p->event->fsm->u.m.lock;
+  SilcFSM fsm;
 
-  /* If the semaphore value has went to zero while we've been waiting this
-     callback, sempahore has been been signalled already.  It can happen
-     when using real threads because the FSM may not be waiting state when
-     the sempahore is posted. */
+  /* We have to check for couple of things before delivering the signal. */
+
+  /* If the event value has went to zero while we've been waiting this
+     callback, the event has been been signalled already.  It can happen
+     when using real threads because the FSM may not be in waiting state
+     when the event is signalled. */
   silc_mutex_lock(lock);
-  if (!p->sema->value) {
+  if (!p->event->value) {
     silc_mutex_unlock(lock);
-    silc_fsm_sema_unref(p->sema);
+    silc_fsm_event_unref(p->event);
+    silc_free(p);
+    return;
+  }
+
+  /* If the waiter is not waiting anymore, don't deliver the signal.  It
+     can happen if there were multiple signallers and the waiter went away
+     after the first signal. */
+  silc_list_start(p->event->waiters);
+  while ((fsm = silc_list_get(p->event->waiters)))
+    if (fsm == p->fsm)
+      break;
+  if (!fsm) {
+    silc_mutex_unlock(lock);
+    silc_fsm_event_unref(p->event);
     silc_free(p);
     return;
   }
@@ -595,64 +693,64 @@ SILC_TASK_CALLBACK(silc_fsm_signal)
   /* Signal */
   silc_fsm_continue_sync(p->fsm);
 
-  silc_fsm_sema_unref(p->sema);
+  silc_fsm_event_unref(p->event);
   silc_free(p);
 }
 
-/* Increase semaphore */
+/* Signal event */
 
-void silc_fsm_sema_post(SilcFSMSema sema)
+void silc_fsm_event_signal(SilcFSMEvent event)
 {
   SilcFSM fsm;
-  SilcFSMSemaPost p;
-  SilcMutex lock = sema->fsm->u.m.lock;
+  SilcFSMEventSignal p;
+  SilcMutex lock = event->fsm->u.m.lock;
 
-  SILC_LOG_DEBUG(("Posting semaphore %p", sema));
+  SILC_LOG_DEBUG(("Signal event %p", event));
 
   silc_mutex_lock(lock);
 
-  sema->value++;
-  silc_list_start(sema->waiters);
-  while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
-    if (fsm->sema) {
-      silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
+  event->value++;
+  silc_list_start(event->waiters);
+  while ((fsm = silc_list_get(event->waiters))) {
+    if (fsm->event) {
+      silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
                                    fsm);
-      fsm->sema = NULL;
+      fsm->event = NULL;
     }
 
     p = silc_calloc(1, sizeof(*p));
-    if (!p)
+    if (silc_unlikely(!p))
       continue;
-    p->sema = sema;
+    p->event = event;
     p->fsm = fsm;
-    silc_fsm_sema_ref(sema);
+    silc_fsm_event_ref(event);
 
     /* Signal through scheduler.  Wake up destination scheduler in case
        caller is a real thread. */
-    silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 1);
+    silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
     silc_schedule_wakeup(fsm->schedule);
   }
 
   silc_mutex_unlock(lock);
 }
 
-/* Post thread termination semaphore.  Special function used only to
+/* Post thread termination event.  Special function used only to
    signal thread termination when SILC_FSM_THREAD_WAIT was used. */
 
-static void silc_fsm_thread_termination_post(SilcFSMSema sema)
+static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
 {
   SilcFSM fsm;
-  SilcMutex lock = sema->fsm->u.m.lock;
+  SilcMutex lock = event->fsm->u.m.lock;
 
-  SILC_LOG_DEBUG(("Post thread termination semaphore %p", sema));
+  SILC_LOG_DEBUG(("Post thread terminate event %p", event));
 
   silc_mutex_lock(lock);
 
-  silc_list_start(sema->waiters);
-  while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
+  silc_list_start(event->waiters);
+  while ((fsm = silc_list_get(event->waiters))) {
     /* Signal on thread termination.  Wake up destination scheduler in case
        caller is a real thread. */
-    silc_list_del(sema->waiters, fsm);
+    silc_list_del(event->waiters, fsm);
     silc_fsm_continue(fsm);
     silc_schedule_wakeup(fsm->schedule);
   }
@@ -662,7 +760,7 @@ static void silc_fsm_thread_termination_post(SilcFSMSema sema)
 
 /* Real thread */
 
-static void *silc_fsm_thread(void *context)
+void *silc_fsm_thread(void *context)
 {
   SilcFSM fsm = context;
   SilcSchedule old = fsm->schedule;
@@ -672,21 +770,39 @@ static void *silc_fsm_thread(void *context)
   /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
      cannot be used in this thread.  Application may still use it if it
      wants but we use our own. */
-  fsm->schedule = silc_schedule_init(0, old);
-  if (!fsm->schedule)
+  fsm->schedule = silc_schedule_init(0, old, silc_schedule_get_stack(old), old);
+  if (silc_unlikely(!fsm->schedule)) {
+    fsm->schedule = old;
     return NULL;
+  }
+
+  /* The new scheduler is a global scheduler in this thread */
+  silc_schedule_set_global(fsm->schedule);
 
   /* Start the FSM thread */
-  if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 1))
+  if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
+                                                   silc_fsm_run, fsm, 0, 0))) {
+    silc_schedule_uninit(fsm->schedule);
+    fsm->schedule = old;
     return NULL;
+  }
 
   /* Run the scheduler */
   silc_schedule(fsm->schedule);
 
+  /* Reset global scheduler */
+  silc_schedule_set_global(NULL);
+
   /* Free resources */
   silc_schedule_uninit(fsm->schedule);
 
   fsm->schedule = old;
 
+  /* Finish the FSM thread in the main thread */
+  SILC_ASSERT(fsm->finished);
+  silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
+                                fsm, 0, 0);
+  silc_schedule_wakeup(fsm->schedule);
+
   return NULL;
 }