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
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;
}
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)
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)
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);
}
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 */
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 */
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 */
void silc_fsm_continue(void *fsm)
{
SilcFSM f = fsm;
+
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 */
SILC_ASSERT(!f->finished);
- /* Machine must not have active threads */
- if (!f->thread && f->u.m.threads)
- assert(f->u.m.threads == 0);
-
f->started = FALSE;
f->finished = TRUE;
}
/* 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. */
fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
} else {
+ /* Machine must not have active threads */
+ assert(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;
SILC_LOG_DEBUG(("Received event %p", event));
- /* It is possible that this FSM is in the list so remove it */
+ /* Remove from waiting */
silc_list_del(event->waiters, fsm);
- event->value--;
+
+ /* Decrease the counter only after all waiters have acquired the signal. */
+ if (!silc_list_count(event->waiters))
+ event->value--;
+
silc_mutex_unlock(lock);
return 1;
}
{
SilcFSMEventSignal p = context;
SilcMutex lock = p->event->fsm->u.m.lock;
+ SilcFSM fsm;
+
+ /* 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 sempahore is posted. */
+ when the event is signalled. */
silc_mutex_lock(lock);
if (!p->event->value) {
silc_mutex_unlock(lock);
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;
+ }
silc_mutex_unlock(lock);
SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
event->value++;
silc_list_start(event->waiters);
- while ((fsm = silc_list_get(event->waiters)) != SILC_LIST_END) {
+ while ((fsm = silc_list_get(event->waiters))) {
if (fsm->event) {
silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
fsm);
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);
/* 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 (silc_unlikely(!fsm->schedule))
+ fsm->schedule = silc_schedule_init(0, old, silc_schedule_get_stack(old));
+ if (silc_unlikely(!fsm->schedule)) {
+ fsm->schedule = old;
return NULL;
+ }
/* Start the FSM thread */
if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
- silc_fsm_run, fsm, 0, 0)))
+ silc_fsm_run, fsm, 0, 0))) {
+ silc_schedule_uninit(fsm->schedule);
+ fsm->schedule = old;
return NULL;
+ }
/* Run the scheduler */
silc_schedule(fsm->schedule);