5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2005 - 2007 Pekka Riikonen
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; version 2 of the License.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
22 SILC_TASK_CALLBACK(silc_fsm_run);
23 SILC_TASK_CALLBACK(silc_fsm_finish_fsm);
24 SILC_TASK_CALLBACK(silc_fsm_event_timedout);
25 SILC_TASK_CALLBACK(silc_fsm_start_real_thread);
26 static void silc_fsm_thread_termination_signal(SilcFSMEvent event);
27 static void silc_fsm_event_ref(SilcFSMEvent event);
28 static void silc_fsm_event_unref(SilcFSMEvent event);
29 void *silc_fsm_thread(void *context);
33 SilcFSM silc_fsm_alloc(void *fsm_context,
34 SilcFSMDestructor destructor,
35 void *destructor_context,
36 SilcSchedule schedule)
40 fsm = silc_calloc(1, sizeof(*fsm));
41 if (silc_unlikely(!fsm))
44 if (silc_unlikely(!silc_fsm_init(fsm, fsm_context, destructor,
45 destructor_context, schedule))) {
55 SilcBool silc_fsm_init(SilcFSM fsm,
57 SilcFSMDestructor destructor,
58 void *destructor_context,
59 SilcSchedule schedule)
64 fsm->fsm_context = fsm_context;
65 fsm->state_context = NULL;
66 fsm->destructor = destructor;
67 fsm->destructor_context = destructor_context;
68 fsm->schedule = schedule;
70 fsm->async_call = FALSE;
73 silc_atomic_init32(&fsm->u.m.threads, 0);
78 /* Allocate FSM thread. Internally machine and thread use same context. */
80 SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
82 SilcFSMThreadDestructor destructor,
83 void *destructor_context,
88 thread = silc_calloc(1, sizeof(*thread));
89 if (silc_unlikely(!thread))
92 silc_fsm_thread_init(thread, fsm, thread_context, destructor,
93 destructor_context, real_thread);
97 /* Initialize FSM thread. Internally machine and thread use same context. */
99 void silc_fsm_thread_init(SilcFSMThread thread,
101 void *thread_context,
102 SilcFSMThreadDestructor destructor,
103 void *destructor_context,
104 SilcBool real_thread)
106 SILC_LOG_DEBUG(("Initializing new thread %p (%s)",
107 thread, real_thread ? "real" : "FSM"));
109 SILC_ASSERT(!fsm->thread);
111 thread->fsm_context = thread_context;
112 thread->state_context = NULL;
113 thread->destructor = (SilcFSMDestructor)destructor;
114 thread->destructor_context = destructor_context;
115 thread->schedule = fsm->schedule;
116 thread->thread = TRUE;
117 thread->async_call = FALSE;
118 thread->started = FALSE;
119 thread->real_thread = real_thread;
120 thread->u.t.fsm = fsm;
123 silc_atomic_add_int32(&fsm->u.m.threads, 1);
125 /* Allocate lock for the machine if using real threads. */
126 if (real_thread && !fsm->u.m.lock)
127 if (!silc_mutex_alloc(&fsm->u.m.lock))
128 thread->real_thread = FALSE;
131 /* FSM is destroyed through scheduler to make sure that all dying
132 real system threads will have their finish callbacks scheduled before
133 this one (when SILC_FSM_THREAD_WAIT was used). */
135 SILC_TASK_CALLBACK(silc_fsm_free_final)
139 #if defined(SILC_DEBUG)
140 /* We must be finished */
141 SILC_ASSERT(f->finished);
143 /* Machine must not have active threads */
144 if (!f->thread && silc_atomic_get_int32(&f->u.m.threads))
145 SILC_ASSERT(silc_atomic_get_int32(&f->u.m.threads) == 0);
146 #endif /* SILC_DEBUG */
148 if (!f->thread && f->u.m.lock)
149 silc_mutex_free(f->u.m.lock);
151 if (f->thread && f->u.t.event)
152 silc_fsm_event_free(f->u.t.event);
155 silc_atomic_uninit32(&f->u.m.threads);
162 void silc_fsm_free(void *fsm)
166 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final,
169 silc_fsm_free_final(f->schedule, silc_schedule_get_context(f->schedule),
173 /* Task to start real thread. We start threads through scheduler, not
174 directly in silc_fsm_start. */
176 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
181 if (silc_thread_create(silc_fsm_thread, f, FALSE))
183 #endif /* SILC_THREADS */
185 SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
187 /* Normal FSM operation */
188 f->real_thread = FALSE;
189 silc_fsm_continue_sync(f);
192 /* Start FSM in the specified state */
194 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
198 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
201 f->next_state = start_state;
202 f->synchronous = FALSE;
205 /* Start real thread through scheduler */
206 if (f->thread && f->real_thread) {
207 if (!silc_schedule_task_add_timeout(f->schedule,
208 silc_fsm_start_real_thread,
210 silc_fsm_start_real_thread(f->schedule,
211 silc_schedule_get_context(f->schedule),
213 silc_schedule_wakeup(f->schedule);
217 /* Normal FSM operation */
218 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
219 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
221 /* Wakeup scheduler in case we are starting this thread from another
224 silc_schedule_wakeup(f->schedule);
227 /* Start FSM in the specified state synchronously */
229 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
233 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
236 f->next_state = start_state;
237 f->synchronous = TRUE;
240 /* Start real thread directly */
241 if (f->thread && f->real_thread) {
242 silc_fsm_start_real_thread(f->schedule,
243 silc_schedule_get_context(f->schedule),
248 /* Normal FSM operation */
249 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
252 /* Set next FSM state */
254 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
257 f->next_state = next_state;
260 /* Continue after timeout */
262 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
263 SilcUInt32 seconds, SilcUInt32 useconds)
267 f->next_state = next_state;
268 if (!seconds && !useconds)
271 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
273 f->next_later = TRUE;
275 /* Wakeup up the scheduler just in case this was called from another
277 silc_schedule_wakeup(f->schedule);
280 /* Continue after callback or async operation */
282 void silc_fsm_continue(void *fsm)
287 /* Cancel next_later timeout */
288 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
289 f->next_later = FALSE;
292 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
293 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
295 /* Wakeup up the scheduler just in case this was called from another
297 silc_schedule_wakeup(f->schedule);
300 /* Continue after callback or async operation immediately */
302 void silc_fsm_continue_sync(void *fsm)
306 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
307 f->next_later = FALSE;
309 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
314 void silc_fsm_finish(void *fsm)
318 SILC_ASSERT(!f->finished);
320 /* Machine must not have active threads */
321 if (!f->thread && silc_atomic_get_int32(&f->u.m.threads))
322 assert(silc_atomic_get_int32(&f->u.m.threads) == 0);
327 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
328 f->next_later = FALSE;
330 /* If we are thread and using real threads, the FSM thread will finish
331 after the real thread has finished, in the main thread. */
332 if (f->thread && f->real_thread) {
333 /* Stop the real thread's scheduler to finish the thread */
334 silc_schedule_stop(f->schedule);
335 silc_schedule_wakeup(f->schedule);
339 /* Normal FSM operation */
341 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm,
345 silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
349 /* Return associated scheduler */
351 SilcSchedule silc_fsm_get_schedule(void *fsm)
357 /* Return thread's machine */
359 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
361 SILC_ASSERT(thread->thread);
362 return (SilcFSM)thread->u.t.fsm;
365 /* Returns TRUE if FSM is started */
367 SilcBool silc_fsm_is_started(void *fsm)
375 void silc_fsm_set_context(void *fsm, void *fsm_context)
378 f->fsm_context = fsm_context;
383 void *silc_fsm_get_context(void *fsm)
386 return f->fsm_context;
389 /* Set state context */
391 void silc_fsm_set_state_context(void *fsm, void *state_context)
394 f->state_context = state_context;
397 /* Get state context */
399 void *silc_fsm_get_state_context(void *fsm)
402 return f->state_context;
405 /* Wait for thread to terminate */
407 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
411 SILC_ASSERT(t->thread);
413 t->u.t.event = silc_fsm_event_alloc(t->u.t.fsm);
417 SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
418 silc_fsm_event_wait(t->u.t.event, fsm);
424 SILC_TASK_CALLBACK(silc_fsm_run)
426 SilcFSM fsm = context;
427 SilcFSMStatus status;
429 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
433 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
434 while (status == SILC_FSM_ST_CONTINUE);
437 case SILC_FSM_ST_YIELD:
438 /* Continue through scheduler */
439 silc_fsm_continue(fsm);
442 case SILC_FSM_ST_WAIT:
443 /* The machine is in hold */
444 SILC_LOG_DEBUG(("State wait %p", fsm));
445 fsm->synchronous = FALSE;
448 case SILC_FSM_ST_FINISH:
449 /* Finish the state machine */
450 SILC_LOG_DEBUG(("State finish %p", fsm));
451 silc_fsm_finish(fsm);
459 /* Finishes the FSM. This is always executed in the main thread, even
460 for FSM threads that were run in real threads. */
462 SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
464 SilcFSM fsm = context;
466 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
468 fsm->next_state = NULL;
471 /* This is thread, send signal */
472 if (fsm->u.t.event) {
473 silc_fsm_thread_termination_signal(fsm->u.t.event);
474 silc_fsm_event_free(fsm->u.t.event);
475 fsm->u.t.event = NULL;
478 /* Remove the thread from machine */
479 silc_atomic_sub_int32(&fsm->u.t.fsm->u.m.threads, 1);
481 /* Call the destructor callback only if the underlaying machine is
483 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
484 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
488 silc_mutex_free(fsm->u.m.lock);
489 fsm->u.m.lock = NULL;
492 /* Call the destructor callback. */
494 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
498 /* Allocate FSM event */
500 SilcFSMEvent silc_fsm_event_alloc(SilcFSM fsm)
504 event = silc_calloc(1, sizeof(*event));
505 if (silc_unlikely(!event))
508 silc_fsm_event_init(event, fsm);
509 event->allocated = TRUE;
514 /* Initializes FSM event */
516 void silc_fsm_event_init(SilcFSMEvent event, SilcFSM fsm)
518 SILC_LOG_DEBUG(("Initializing event %p", event));
519 SILC_ASSERT(!fsm->thread);
520 memset(event, 0, sizeof(*event));
523 silc_list_init(event->waiters, struct SilcFSMObject, next);
528 void silc_fsm_event_free(SilcFSMEvent event)
530 if (event->refcnt > 0)
532 if (silc_list_count(event->waiters) > 0)
537 /* Reference event */
539 static void silc_fsm_event_ref(SilcFSMEvent event)
544 /* Unreference event */
546 static void silc_fsm_event_unref(SilcFSMEvent event)
549 if (event->refcnt == 0 && event->allocated)
550 silc_fsm_event_free(event);
553 /* Wait until event is non-zero. */
555 SilcUInt32 silc_fsm_event_wait(SilcFSMEvent event, void *fsm)
557 SilcMutex lock = event->fsm->u.m.lock;
559 silc_mutex_lock(lock);
562 #if defined(SILC_DEBUG)
564 silc_list_start(event->waiters);
565 while ((entry = silc_list_get(event->waiters)))
566 SILC_ASSERT(entry != fsm);
567 #endif /* SILC_DEBUG */
569 SILC_LOG_DEBUG(("Waiting for event %p", event));
571 /* Add the FSM to waiter list */
572 silc_list_add(event->waiters, fsm);
573 silc_mutex_unlock(lock);
577 SILC_LOG_DEBUG(("Received event %p", event));
579 /* Remove from waiting */
580 silc_list_del(event->waiters, fsm);
582 /* Decrease the counter only after all waiters have acquired the signal. */
583 if (!silc_list_count(event->waiters))
586 silc_mutex_unlock(lock);
590 /* Wait util event is non-zero, or timeout occurs. */
592 SilcUInt32 silc_fsm_event_timedwait(SilcFSMEvent event, void *fsm,
593 SilcUInt32 seconds, SilcUInt32 useconds,
596 SilcMutex lock = event->fsm->u.m.lock;
600 silc_mutex_lock(lock);
602 if (f->event_timedout) {
603 SILC_LOG_DEBUG(("Event waiting timedout"));
604 f->event_timedout = FALSE;
607 silc_mutex_unlock(lock);
611 silc_mutex_unlock(lock);
613 value = silc_fsm_event_wait(event, fsm);
615 silc_schedule_task_add_timeout(f->schedule, silc_fsm_event_timedout,
616 f, seconds, useconds);
628 SILC_TASK_CALLBACK(silc_fsm_event_timedout)
630 SilcFSM fsm = context;
631 SilcMutex lock = fsm->event->fsm->u.m.lock;
633 SILC_LOG_DEBUG(("Event %p timedout", fsm->event));
635 /* Remove the waiter from the event waiters list */
636 silc_mutex_lock(lock);
637 silc_list_del(fsm->event->waiters, fsm);
641 silc_fsm_continue(fsm);
642 fsm->event_timedout = TRUE;
646 silc_mutex_unlock(lock);
649 /* Signalled, event */
651 SILC_TASK_CALLBACK(silc_fsm_signal)
653 SilcFSMEventSignal p = context;
654 SilcMutex lock = p->event->fsm->u.m.lock;
657 /* We have to check for couple of things before delivering the signal. */
659 /* If the event value has went to zero while we've been waiting this
660 callback, the event has been been signalled already. It can happen
661 when using real threads because the FSM may not be in waiting state
662 when the event is signalled. */
663 silc_mutex_lock(lock);
664 if (!p->event->value) {
665 silc_mutex_unlock(lock);
666 silc_fsm_event_unref(p->event);
671 /* If the waiter is not waiting anymore, don't deliver the signal. It
672 can happen if there were multiple signallers and the waiter went away
673 after the first signal. */
674 silc_list_start(p->event->waiters);
675 while ((fsm = silc_list_get(p->event->waiters)))
679 silc_mutex_unlock(lock);
680 silc_fsm_event_unref(p->event);
684 silc_mutex_unlock(lock);
686 SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
690 silc_fsm_continue_sync(p->fsm);
692 silc_fsm_event_unref(p->event);
698 void silc_fsm_event_signal(SilcFSMEvent event)
701 SilcFSMEventSignal p;
702 SilcMutex lock = event->fsm->u.m.lock;
704 SILC_LOG_DEBUG(("Signal event %p", event));
706 silc_mutex_lock(lock);
709 silc_list_start(event->waiters);
710 while ((fsm = silc_list_get(event->waiters))) {
712 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
717 p = silc_calloc(1, sizeof(*p));
718 if (silc_unlikely(!p))
722 silc_fsm_event_ref(event);
724 /* Signal through scheduler. Wake up destination scheduler in case
725 caller is a real thread. */
726 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
727 silc_schedule_wakeup(fsm->schedule);
730 silc_mutex_unlock(lock);
733 /* Post thread termination event. Special function used only to
734 signal thread termination when SILC_FSM_THREAD_WAIT was used. */
736 static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
739 SilcMutex lock = event->fsm->u.m.lock;
741 SILC_LOG_DEBUG(("Post thread terminate event %p", event));
743 silc_mutex_lock(lock);
745 silc_list_start(event->waiters);
746 while ((fsm = silc_list_get(event->waiters))) {
747 /* Signal on thread termination. Wake up destination scheduler in case
748 caller is a real thread. */
749 silc_list_del(event->waiters, fsm);
750 silc_fsm_continue(fsm);
751 silc_schedule_wakeup(fsm->schedule);
754 silc_mutex_unlock(lock);
759 void *silc_fsm_thread(void *context)
761 SilcFSM fsm = context;
762 SilcSchedule old = fsm->schedule;
764 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
766 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
767 cannot be used in this thread. Application may still use it if it
768 wants but we use our own. */
769 fsm->schedule = silc_schedule_init(0, old);
770 if (silc_unlikely(!fsm->schedule))
773 /* Start the FSM thread */
774 if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
775 silc_fsm_run, fsm, 0, 0)))
778 /* Run the scheduler */
779 silc_schedule(fsm->schedule);
782 silc_schedule_uninit(fsm->schedule);
786 /* Finish the FSM thread in the main thread */
787 SILC_ASSERT(fsm->finished);
788 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
790 silc_schedule_wakeup(fsm->schedule);