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)
62 schedule = silc_schedule_get_global();
64 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
69 fsm->fsm_context = fsm_context;
70 fsm->state_context = NULL;
71 fsm->destructor = destructor;
72 fsm->destructor_context = destructor_context;
73 fsm->schedule = schedule;
75 fsm->async_call = FALSE;
78 silc_atomic_init32(&fsm->u.m.threads, 0);
83 /* Allocate FSM thread. Internally machine and thread use same context. */
85 SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
87 SilcFSMThreadDestructor destructor,
88 void *destructor_context,
93 thread = silc_calloc(1, sizeof(*thread));
94 if (silc_unlikely(!thread))
97 silc_fsm_thread_init(thread, fsm, thread_context, destructor,
98 destructor_context, real_thread);
102 /* Initialize FSM thread. Internally machine and thread use same context. */
104 void silc_fsm_thread_init(SilcFSMThread thread,
106 void *thread_context,
107 SilcFSMThreadDestructor destructor,
108 void *destructor_context,
109 SilcBool real_thread)
111 SILC_LOG_DEBUG(("Initializing new thread %p (%s)",
112 thread, real_thread ? "real" : "FSM"));
114 SILC_VERIFY(!fsm->thread);
116 thread->fsm_context = thread_context;
117 thread->state_context = NULL;
118 thread->destructor = (SilcFSMDestructor)destructor;
119 thread->destructor_context = destructor_context;
120 thread->schedule = fsm->schedule;
121 thread->thread = TRUE;
122 thread->async_call = FALSE;
123 thread->started = FALSE;
124 thread->real_thread = real_thread;
125 thread->u.t.fsm = fsm;
128 silc_atomic_add_int32(&fsm->u.m.threads, 1);
130 /* Allocate lock for the machine if using real threads. */
131 if (real_thread && !fsm->u.m.lock)
132 if (!silc_mutex_alloc(&fsm->u.m.lock))
133 thread->real_thread = FALSE;
136 /* FSM is destroyed through scheduler to make sure that all dying
137 real system threads will have their finish callbacks scheduled before
138 this one (when SILC_FSM_THREAD_WAIT was used). */
140 SILC_TASK_CALLBACK(silc_fsm_free_final)
144 #if defined(SILC_DEBUG)
145 /* We must be finished */
146 SILC_ASSERT(f->finished);
148 /* Machine must not have active threads */
149 if (!f->thread && silc_atomic_get_int32(&f->u.m.threads))
150 SILC_ASSERT(silc_atomic_get_int32(&f->u.m.threads) == 0);
151 #endif /* SILC_DEBUG */
153 if (!f->thread && f->u.m.lock)
154 silc_mutex_free(f->u.m.lock);
156 if (f->thread && f->u.t.event)
157 silc_fsm_event_free(f->u.t.event);
160 silc_atomic_uninit32(&f->u.m.threads);
167 void silc_fsm_free(void *fsm)
171 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final,
174 silc_fsm_free_final(f->schedule, silc_schedule_get_context(f->schedule),
178 /* Task to start real thread. We start threads through scheduler, not
179 directly in silc_fsm_start. */
181 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
186 if (silc_thread_create(silc_fsm_thread, f, FALSE))
188 #endif /* SILC_THREADS */
190 SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
192 /* Normal FSM operation */
193 f->real_thread = FALSE;
194 silc_fsm_continue_sync(f);
197 /* Start FSM in the specified state */
199 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
203 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
206 f->next_state = start_state;
207 f->synchronous = FALSE;
210 /* Start real thread through scheduler */
211 if (f->thread && f->real_thread) {
212 if (!silc_schedule_task_add_timeout(f->schedule,
213 silc_fsm_start_real_thread,
215 silc_fsm_start_real_thread(f->schedule,
216 silc_schedule_get_context(f->schedule),
218 silc_schedule_wakeup(f->schedule);
222 /* Normal FSM operation */
223 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
224 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
226 /* Wakeup scheduler in case we are starting this thread from another
229 silc_schedule_wakeup(f->schedule);
232 /* Start FSM in the specified state synchronously */
234 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
238 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
241 f->next_state = start_state;
242 f->synchronous = TRUE;
245 /* Start real thread directly */
246 if (f->thread && f->real_thread) {
247 silc_fsm_start_real_thread(f->schedule,
248 silc_schedule_get_context(f->schedule),
253 /* Normal FSM operation */
254 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
257 /* Set next FSM state */
259 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
262 f->next_state = next_state;
265 /* Continue after timeout */
267 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
268 SilcUInt32 seconds, SilcUInt32 useconds)
272 f->next_state = next_state;
273 if (!seconds && !useconds)
276 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
278 f->next_later = TRUE;
280 /* Wakeup up the scheduler just in case this was called from another
282 silc_schedule_wakeup(f->schedule);
285 /* Continue after callback or async operation */
287 void silc_fsm_continue(void *fsm)
292 /* Cancel next_later timeout */
293 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
294 f->next_later = FALSE;
297 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
298 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
300 /* Wakeup up the scheduler just in case this was called from another
302 silc_schedule_wakeup(f->schedule);
305 /* Continue after callback or async operation immediately */
307 void silc_fsm_continue_sync(void *fsm)
311 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
312 f->next_later = FALSE;
314 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
319 void silc_fsm_finish(void *fsm)
323 SILC_VERIFY(!f->finished);
328 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
329 f->next_later = FALSE;
331 /* If we are thread and using real threads, the FSM thread will finish
332 after the real thread has finished, in the main thread. */
333 if (f->thread && f->real_thread) {
334 /* Stop the real thread's scheduler to finish the thread */
335 silc_schedule_stop(f->schedule);
336 silc_schedule_wakeup(f->schedule);
340 /* Normal FSM operation */
342 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm,
346 silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
350 /* Return associated scheduler */
352 SilcSchedule silc_fsm_get_schedule(void *fsm)
358 /* Return thread's machine */
360 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
362 SILC_VERIFY(thread->thread);
363 return (SilcFSM)thread->u.t.fsm;
366 /* Returns TRUE if FSM is started */
368 SilcBool silc_fsm_is_started(void *fsm)
376 void silc_fsm_set_context(void *fsm, void *fsm_context)
379 f->fsm_context = fsm_context;
384 void *silc_fsm_get_context(void *fsm)
387 return f->fsm_context;
390 /* Set state context */
392 void silc_fsm_set_state_context(void *fsm, void *state_context)
395 f->state_context = state_context;
398 /* Get state context */
400 void *silc_fsm_get_state_context(void *fsm)
403 return f->state_context;
406 /* Wait for thread to terminate */
408 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
412 SILC_VERIFY(t->thread);
414 t->u.t.event = silc_fsm_event_alloc(t->u.t.fsm);
418 SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
419 silc_fsm_event_wait(t->u.t.event, fsm);
425 SILC_TASK_CALLBACK(silc_fsm_run)
427 SilcFSM fsm = context;
428 SilcFSMStatus status;
430 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
434 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
435 while (status == SILC_FSM_ST_CONTINUE);
438 case SILC_FSM_ST_YIELD:
439 /* Continue through scheduler */
440 silc_fsm_continue(fsm);
443 case SILC_FSM_ST_WAIT:
444 /* The machine is in hold */
445 SILC_LOG_DEBUG(("State wait %p", fsm));
446 fsm->synchronous = FALSE;
449 case SILC_FSM_ST_FINISH:
450 /* Finish the state machine */
451 SILC_LOG_DEBUG(("State finish %p", fsm));
452 silc_fsm_finish(fsm);
460 /* Finishes the FSM. This is always executed in the main thread, even
461 for FSM threads that were run in real threads. */
463 SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
465 SilcFSM fsm = context;
467 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
469 fsm->next_state = NULL;
472 /* This is thread, send signal */
473 if (fsm->u.t.event) {
474 silc_fsm_thread_termination_signal(fsm->u.t.event);
475 silc_fsm_event_free(fsm->u.t.event);
476 fsm->u.t.event = NULL;
479 /* Remove the thread from machine */
480 silc_atomic_sub_int32(&fsm->u.t.fsm->u.m.threads, 1);
482 /* Call the destructor callback only if the underlaying machine is
484 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
485 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
488 /* Machine must not have active threads */
489 SILC_VERIFY(silc_atomic_get_int32(&fsm->u.m.threads) == 0);
492 silc_mutex_free(fsm->u.m.lock);
493 fsm->u.m.lock = NULL;
496 /* Call the destructor callback. */
498 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
502 /* Allocate FSM event */
504 SilcFSMEvent silc_fsm_event_alloc(SilcFSM fsm)
508 event = silc_calloc(1, sizeof(*event));
509 if (silc_unlikely(!event))
512 silc_fsm_event_init(event, fsm);
513 event->allocated = TRUE;
518 /* Initializes FSM event */
520 void silc_fsm_event_init(SilcFSMEvent event, SilcFSM fsm)
522 SILC_LOG_DEBUG(("Initializing event %p", event));
523 SILC_VERIFY(!fsm->thread);
524 memset(event, 0, sizeof(*event));
527 silc_list_init(event->waiters, struct SilcFSMObject, next);
532 void silc_fsm_event_free(SilcFSMEvent event)
534 if (event->refcnt > 0)
536 if (silc_list_count(event->waiters) > 0)
541 /* Reference event */
543 static void silc_fsm_event_ref(SilcFSMEvent event)
548 /* Unreference event */
550 static void silc_fsm_event_unref(SilcFSMEvent event)
553 if (event->refcnt == 0 && event->allocated)
554 silc_fsm_event_free(event);
557 /* Wait until event is non-zero. */
559 SilcUInt32 silc_fsm_event_wait(SilcFSMEvent event, void *fsm)
561 SilcMutex lock = event->fsm->u.m.lock;
563 silc_mutex_lock(lock);
566 #if defined(SILC_DEBUG)
568 silc_list_start(event->waiters);
569 while ((entry = silc_list_get(event->waiters)))
570 SILC_ASSERT(entry != fsm);
571 #endif /* SILC_DEBUG */
573 SILC_LOG_DEBUG(("Waiting for event %p", event));
575 /* Add the FSM to waiter list */
576 silc_list_add(event->waiters, fsm);
577 silc_mutex_unlock(lock);
581 SILC_LOG_DEBUG(("Received event %p", event));
583 /* Remove from waiting */
584 silc_list_del(event->waiters, fsm);
586 /* Decrease the counter only after all waiters have acquired the signal. */
587 if (!silc_list_count(event->waiters))
590 silc_mutex_unlock(lock);
594 /* Wait util event is non-zero, or timeout occurs. */
596 SilcUInt32 silc_fsm_event_timedwait(SilcFSMEvent event, void *fsm,
597 SilcUInt32 seconds, SilcUInt32 useconds,
600 SilcMutex lock = event->fsm->u.m.lock;
604 silc_mutex_lock(lock);
606 if (f->event_timedout) {
607 SILC_LOG_DEBUG(("Event waiting timedout"));
608 f->event_timedout = FALSE;
611 silc_mutex_unlock(lock);
615 silc_mutex_unlock(lock);
617 value = silc_fsm_event_wait(event, fsm);
619 silc_schedule_task_add_timeout(f->schedule, silc_fsm_event_timedout,
620 f, seconds, useconds);
632 SILC_TASK_CALLBACK(silc_fsm_event_timedout)
634 SilcFSM fsm = context;
635 SilcMutex lock = fsm->event->fsm->u.m.lock;
637 SILC_LOG_DEBUG(("Event %p timedout", fsm->event));
639 /* Remove the waiter from the event waiters list */
640 silc_mutex_lock(lock);
641 silc_list_del(fsm->event->waiters, fsm);
645 silc_fsm_continue(fsm);
646 fsm->event_timedout = TRUE;
650 silc_mutex_unlock(lock);
653 /* Signalled, event */
655 SILC_TASK_CALLBACK(silc_fsm_signal)
657 SilcFSMEventSignal p = context;
658 SilcMutex lock = p->event->fsm->u.m.lock;
661 /* We have to check for couple of things before delivering the signal. */
663 /* If the event value has went to zero while we've been waiting this
664 callback, the event has been been signalled already. It can happen
665 when using real threads because the FSM may not be in waiting state
666 when the event is signalled. */
667 silc_mutex_lock(lock);
668 if (!p->event->value) {
669 silc_mutex_unlock(lock);
670 silc_fsm_event_unref(p->event);
675 /* If the waiter is not waiting anymore, don't deliver the signal. It
676 can happen if there were multiple signallers and the waiter went away
677 after the first signal. */
678 silc_list_start(p->event->waiters);
679 while ((fsm = silc_list_get(p->event->waiters)))
683 silc_mutex_unlock(lock);
684 silc_fsm_event_unref(p->event);
688 silc_mutex_unlock(lock);
690 SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
694 silc_fsm_continue_sync(p->fsm);
696 silc_fsm_event_unref(p->event);
702 void silc_fsm_event_signal(SilcFSMEvent event)
705 SilcFSMEventSignal p;
706 SilcMutex lock = event->fsm->u.m.lock;
708 SILC_LOG_DEBUG(("Signal event %p", event));
710 silc_mutex_lock(lock);
713 silc_list_start(event->waiters);
714 while ((fsm = silc_list_get(event->waiters))) {
716 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
721 p = silc_calloc(1, sizeof(*p));
722 if (silc_unlikely(!p))
726 silc_fsm_event_ref(event);
728 /* Signal through scheduler. Wake up destination scheduler in case
729 caller is a real thread. */
730 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
731 silc_schedule_wakeup(fsm->schedule);
734 silc_mutex_unlock(lock);
737 /* Post thread termination event. Special function used only to
738 signal thread termination when SILC_FSM_THREAD_WAIT was used. */
740 static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
743 SilcMutex lock = event->fsm->u.m.lock;
745 SILC_LOG_DEBUG(("Post thread terminate event %p", event));
747 silc_mutex_lock(lock);
749 silc_list_start(event->waiters);
750 while ((fsm = silc_list_get(event->waiters))) {
751 /* Signal on thread termination. Wake up destination scheduler in case
752 caller is a real thread. */
753 silc_list_del(event->waiters, fsm);
754 silc_fsm_continue(fsm);
755 silc_schedule_wakeup(fsm->schedule);
758 silc_mutex_unlock(lock);
763 void *silc_fsm_thread(void *context)
765 SilcFSM fsm = context;
766 SilcSchedule old = fsm->schedule;
768 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
770 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
771 cannot be used in this thread. Application may still use it if it
772 wants but we use our own. */
773 fsm->schedule = silc_schedule_init(0, old, silc_schedule_get_stack(old), old);
774 if (silc_unlikely(!fsm->schedule)) {
779 /* The new scheduler is a global scheduler in this thread */
780 silc_schedule_set_global(fsm->schedule);
782 /* Start the FSM thread */
783 if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
784 silc_fsm_run, fsm, 0, 0))) {
785 silc_schedule_uninit(fsm->schedule);
790 /* Run the scheduler */
791 silc_schedule(fsm->schedule);
793 /* Reset global scheduler */
794 silc_schedule_set_global(NULL);
797 silc_schedule_uninit(fsm->schedule);
801 /* Finish the FSM thread in the main thread */
802 SILC_ASSERT(fsm->finished);
803 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
805 silc_schedule_wakeup(fsm->schedule);