Do not deliver event signal if waiter has gone away.
[silc.git] / lib / silcutil / silcfsm.c
index 9ba6466f3f5577db8d7f90fa8f8a55a687c89638..aff9a77edf5e1cf8d7c053083289e06fcf706475 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
@@ -69,8 +69,8 @@ SilcBool silc_fsm_init(SilcFSM fsm,
   fsm->thread = FALSE;
   fsm->async_call = FALSE;
   fsm->started = FALSE;
-  fsm->u.m.threads = 0;
   fsm->u.m.lock = NULL;
+  silc_atomic_init32(&fsm->u.m.threads, 0);
 
   return TRUE;
 }
@@ -120,7 +120,7 @@ void silc_fsm_thread_init(SilcFSMThread 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)
@@ -141,8 +141,8 @@ SILC_TASK_CALLBACK(silc_fsm_free_final)
   SILC_ASSERT(f->finished);
 
   /* Machine must not have active threads */
-  if (!f->thread && f->u.m.threads)
-    SILC_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)
@@ -213,6 +213,11 @@ void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
   /* Normal FSM operation */
   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 */
@@ -296,8 +301,8 @@ void silc_fsm_finish(void *fsm)
   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))
+    assert(silc_atomic_get_int32(&f->u.m.threads) == 0);
 
   f->started = FALSE;
   f->finished = TRUE;
@@ -407,9 +412,9 @@ SILC_TASK_CALLBACK(silc_fsm_run)
   SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
 
   /* Run the states */
-//  do
+  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_ST_YIELD:
@@ -454,7 +459,7 @@ SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
     }
 
     /* 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. */
@@ -466,6 +471,7 @@ SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
       silc_mutex_free(fsm->u.m.lock);
       fsm->u.m.lock = NULL;
     }
+    silc_atomic_uninit32(&fsm->u.m.threads);
 
     /* Call the destructor callback. */
     if (fsm->destructor)
@@ -626,6 +632,9 @@ SILC_TASK_CALLBACK(silc_fsm_signal)
 {
   SilcFSMEventSignal p = context;
   SilcMutex lock = p->event->fsm->u.m.lock;
+  SilcFSM fsm;
+
+  /* We have to check 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
@@ -638,6 +647,18 @@ SILC_TASK_CALLBACK(silc_fsm_signal)
     silc_free(p);
     return;
   }
+
+  /* If the waiter is not waiting anymore, don't deliver the 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;
+  }
   silc_mutex_unlock(lock);
 
   SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
@@ -700,7 +721,7 @@ static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
   silc_mutex_lock(lock);
 
   silc_list_start(event->waiters);
-  while ((fsm = silc_list_get(event->waiters)) != SILC_LIST_END) {
+  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(event->waiters, fsm);