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 silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
66 fsm->fsm_context = fsm_context;
67 fsm->state_context = NULL;
68 fsm->destructor = destructor;
69 fsm->destructor_context = destructor_context;
70 fsm->schedule = schedule;
72 fsm->async_call = FALSE;
75 silc_atomic_init32(&fsm->u.m.threads, 0);
80 /* Allocate FSM thread. Internally machine and thread use same context. */
82 SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
84 SilcFSMThreadDestructor destructor,
85 void *destructor_context,
90 thread = silc_calloc(1, sizeof(*thread));
91 if (silc_unlikely(!thread))
94 silc_fsm_thread_init(thread, fsm, thread_context, destructor,
95 destructor_context, real_thread);
99 /* Initialize FSM thread. Internally machine and thread use same context. */
101 void silc_fsm_thread_init(SilcFSMThread thread,
103 void *thread_context,
104 SilcFSMThreadDestructor destructor,
105 void *destructor_context,
106 SilcBool real_thread)
108 SILC_LOG_DEBUG(("Initializing new thread %p (%s)",
109 thread, real_thread ? "real" : "FSM"));
111 SILC_ASSERT(!fsm->thread);
113 thread->fsm_context = thread_context;
114 thread->state_context = NULL;
115 thread->destructor = (SilcFSMDestructor)destructor;
116 thread->destructor_context = destructor_context;
117 thread->schedule = fsm->schedule;
118 thread->thread = TRUE;
119 thread->async_call = FALSE;
120 thread->started = FALSE;
121 thread->real_thread = real_thread;
122 thread->u.t.fsm = fsm;
125 silc_atomic_add_int32(&fsm->u.m.threads, 1);
127 /* Allocate lock for the machine if using real threads. */
128 if (real_thread && !fsm->u.m.lock)
129 if (!silc_mutex_alloc(&fsm->u.m.lock))
130 thread->real_thread = FALSE;
133 /* FSM is destroyed through scheduler to make sure that all dying
134 real system threads will have their finish callbacks scheduled before
135 this one (when SILC_FSM_THREAD_WAIT was used). */
137 SILC_TASK_CALLBACK(silc_fsm_free_final)
141 #if defined(SILC_DEBUG)
142 /* We must be finished */
143 SILC_ASSERT(f->finished);
145 /* Machine must not have active threads */
146 if (!f->thread && silc_atomic_get_int32(&f->u.m.threads))
147 SILC_ASSERT(silc_atomic_get_int32(&f->u.m.threads) == 0);
148 #endif /* SILC_DEBUG */
150 if (!f->thread && f->u.m.lock)
151 silc_mutex_free(f->u.m.lock);
153 if (f->thread && f->u.t.event)
154 silc_fsm_event_free(f->u.t.event);
157 silc_atomic_uninit32(&f->u.m.threads);
164 void silc_fsm_free(void *fsm)
168 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final,
171 silc_fsm_free_final(f->schedule, silc_schedule_get_context(f->schedule),
175 /* Task to start real thread. We start threads through scheduler, not
176 directly in silc_fsm_start. */
178 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
183 if (silc_thread_create(silc_fsm_thread, f, FALSE))
185 #endif /* SILC_THREADS */
187 SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
189 /* Normal FSM operation */
190 f->real_thread = FALSE;
191 silc_fsm_continue_sync(f);
194 /* Start FSM in the specified state */
196 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
200 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
203 f->next_state = start_state;
204 f->synchronous = FALSE;
207 /* Start real thread through scheduler */
208 if (f->thread && f->real_thread) {
209 if (!silc_schedule_task_add_timeout(f->schedule,
210 silc_fsm_start_real_thread,
212 silc_fsm_start_real_thread(f->schedule,
213 silc_schedule_get_context(f->schedule),
215 silc_schedule_wakeup(f->schedule);
219 /* Normal FSM operation */
220 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
221 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
223 /* Wakeup scheduler in case we are starting this thread from another
226 silc_schedule_wakeup(f->schedule);
229 /* Start FSM in the specified state synchronously */
231 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
235 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
238 f->next_state = start_state;
239 f->synchronous = TRUE;
242 /* Start real thread directly */
243 if (f->thread && f->real_thread) {
244 silc_fsm_start_real_thread(f->schedule,
245 silc_schedule_get_context(f->schedule),
250 /* Normal FSM operation */
251 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
254 /* Set next FSM state */
256 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
259 f->next_state = next_state;
262 /* Continue after timeout */
264 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
265 SilcUInt32 seconds, SilcUInt32 useconds)
269 f->next_state = next_state;
270 if (!seconds && !useconds)
273 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
275 f->next_later = TRUE;
277 /* Wakeup up the scheduler just in case this was called from another
279 silc_schedule_wakeup(f->schedule);
282 /* Continue after callback or async operation */
284 void silc_fsm_continue(void *fsm)
289 /* Cancel next_later timeout */
290 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
291 f->next_later = FALSE;
294 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
295 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
297 /* Wakeup up the scheduler just in case this was called from another
299 silc_schedule_wakeup(f->schedule);
302 /* Continue after callback or async operation immediately */
304 void silc_fsm_continue_sync(void *fsm)
308 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
309 f->next_later = FALSE;
311 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
316 void silc_fsm_finish(void *fsm)
320 SILC_ASSERT(!f->finished);
325 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
326 f->next_later = FALSE;
328 /* If we are thread and using real threads, the FSM thread will finish
329 after the real thread has finished, in the main thread. */
330 if (f->thread && f->real_thread) {
331 /* Stop the real thread's scheduler to finish the thread */
332 silc_schedule_stop(f->schedule);
333 silc_schedule_wakeup(f->schedule);
337 /* Normal FSM operation */
339 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm,
343 silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
347 /* Return associated scheduler */
349 SilcSchedule silc_fsm_get_schedule(void *fsm)
355 /* Return thread's machine */
357 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
359 SILC_ASSERT(thread->thread);
360 return (SilcFSM)thread->u.t.fsm;
363 /* Returns TRUE if FSM is started */
365 SilcBool silc_fsm_is_started(void *fsm)
373 void silc_fsm_set_context(void *fsm, void *fsm_context)
376 f->fsm_context = fsm_context;
381 void *silc_fsm_get_context(void *fsm)
384 return f->fsm_context;
387 /* Set state context */
389 void silc_fsm_set_state_context(void *fsm, void *state_context)
392 f->state_context = state_context;
395 /* Get state context */
397 void *silc_fsm_get_state_context(void *fsm)
400 return f->state_context;
403 /* Wait for thread to terminate */
405 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
409 SILC_ASSERT(t->thread);
411 t->u.t.event = silc_fsm_event_alloc(t->u.t.fsm);
415 SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
416 silc_fsm_event_wait(t->u.t.event, fsm);
422 SILC_TASK_CALLBACK(silc_fsm_run)
424 SilcFSM fsm = context;
425 SilcFSMStatus status;
427 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
431 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
432 while (status == SILC_FSM_ST_CONTINUE);
435 case SILC_FSM_ST_YIELD:
436 /* Continue through scheduler */
437 silc_fsm_continue(fsm);
440 case SILC_FSM_ST_WAIT:
441 /* The machine is in hold */
442 SILC_LOG_DEBUG(("State wait %p", fsm));
443 fsm->synchronous = FALSE;
446 case SILC_FSM_ST_FINISH:
447 /* Finish the state machine */
448 SILC_LOG_DEBUG(("State finish %p", fsm));
449 silc_fsm_finish(fsm);
457 /* Finishes the FSM. This is always executed in the main thread, even
458 for FSM threads that were run in real threads. */
460 SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
462 SilcFSM fsm = context;
464 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
466 fsm->next_state = NULL;
469 /* This is thread, send signal */
470 if (fsm->u.t.event) {
471 silc_fsm_thread_termination_signal(fsm->u.t.event);
472 silc_fsm_event_free(fsm->u.t.event);
473 fsm->u.t.event = NULL;
476 /* Remove the thread from machine */
477 silc_atomic_sub_int32(&fsm->u.t.fsm->u.m.threads, 1);
479 /* Call the destructor callback only if the underlaying machine is
481 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
482 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
485 /* Machine must not have active threads */
486 SILC_VERIFY(silc_atomic_get_int32(&fsm->u.m.threads) == 0);
489 silc_mutex_free(fsm->u.m.lock);
490 fsm->u.m.lock = NULL;
493 /* Call the destructor callback. */
495 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
499 /* Allocate FSM event */
501 SilcFSMEvent silc_fsm_event_alloc(SilcFSM fsm)
505 event = silc_calloc(1, sizeof(*event));
506 if (silc_unlikely(!event))
509 silc_fsm_event_init(event, fsm);
510 event->allocated = TRUE;
515 /* Initializes FSM event */
517 void silc_fsm_event_init(SilcFSMEvent event, SilcFSM fsm)
519 SILC_LOG_DEBUG(("Initializing event %p", event));
520 SILC_ASSERT(!fsm->thread);
521 memset(event, 0, sizeof(*event));
524 silc_list_init(event->waiters, struct SilcFSMObject, next);
529 void silc_fsm_event_free(SilcFSMEvent event)
531 if (event->refcnt > 0)
533 if (silc_list_count(event->waiters) > 0)
538 /* Reference event */
540 static void silc_fsm_event_ref(SilcFSMEvent event)
545 /* Unreference event */
547 static void silc_fsm_event_unref(SilcFSMEvent event)
550 if (event->refcnt == 0 && event->allocated)
551 silc_fsm_event_free(event);
554 /* Wait until event is non-zero. */
556 SilcUInt32 silc_fsm_event_wait(SilcFSMEvent event, void *fsm)
558 SilcMutex lock = event->fsm->u.m.lock;
560 silc_mutex_lock(lock);
563 #if defined(SILC_DEBUG)
565 silc_list_start(event->waiters);
566 while ((entry = silc_list_get(event->waiters)))
567 SILC_ASSERT(entry != fsm);
568 #endif /* SILC_DEBUG */
570 SILC_LOG_DEBUG(("Waiting for event %p", event));
572 /* Add the FSM to waiter list */
573 silc_list_add(event->waiters, fsm);
574 silc_mutex_unlock(lock);
578 SILC_LOG_DEBUG(("Received event %p", event));
580 /* Remove from waiting */
581 silc_list_del(event->waiters, fsm);
583 /* Decrease the counter only after all waiters have acquired the signal. */
584 if (!silc_list_count(event->waiters))
587 silc_mutex_unlock(lock);
591 /* Wait util event is non-zero, or timeout occurs. */
593 SilcUInt32 silc_fsm_event_timedwait(SilcFSMEvent event, void *fsm,
594 SilcUInt32 seconds, SilcUInt32 useconds,
597 SilcMutex lock = event->fsm->u.m.lock;
601 silc_mutex_lock(lock);
603 if (f->event_timedout) {
604 SILC_LOG_DEBUG(("Event waiting timedout"));
605 f->event_timedout = FALSE;
608 silc_mutex_unlock(lock);
612 silc_mutex_unlock(lock);
614 value = silc_fsm_event_wait(event, fsm);
616 silc_schedule_task_add_timeout(f->schedule, silc_fsm_event_timedout,
617 f, seconds, useconds);
629 SILC_TASK_CALLBACK(silc_fsm_event_timedout)
631 SilcFSM fsm = context;
632 SilcMutex lock = fsm->event->fsm->u.m.lock;
634 SILC_LOG_DEBUG(("Event %p timedout", fsm->event));
636 /* Remove the waiter from the event waiters list */
637 silc_mutex_lock(lock);
638 silc_list_del(fsm->event->waiters, fsm);
642 silc_fsm_continue(fsm);
643 fsm->event_timedout = TRUE;
647 silc_mutex_unlock(lock);
650 /* Signalled, event */
652 SILC_TASK_CALLBACK(silc_fsm_signal)
654 SilcFSMEventSignal p = context;
655 SilcMutex lock = p->event->fsm->u.m.lock;
658 /* We have to check for couple of things before delivering the signal. */
660 /* If the event value has went to zero while we've been waiting this
661 callback, the event has been been signalled already. It can happen
662 when using real threads because the FSM may not be in waiting state
663 when the event is signalled. */
664 silc_mutex_lock(lock);
665 if (!p->event->value) {
666 silc_mutex_unlock(lock);
667 silc_fsm_event_unref(p->event);
672 /* If the waiter is not waiting anymore, don't deliver the signal. It
673 can happen if there were multiple signallers and the waiter went away
674 after the first signal. */
675 silc_list_start(p->event->waiters);
676 while ((fsm = silc_list_get(p->event->waiters)))
680 silc_mutex_unlock(lock);
681 silc_fsm_event_unref(p->event);
685 silc_mutex_unlock(lock);
687 SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
691 silc_fsm_continue_sync(p->fsm);
693 silc_fsm_event_unref(p->event);
699 void silc_fsm_event_signal(SilcFSMEvent event)
702 SilcFSMEventSignal p;
703 SilcMutex lock = event->fsm->u.m.lock;
705 SILC_LOG_DEBUG(("Signal event %p", event));
707 silc_mutex_lock(lock);
710 silc_list_start(event->waiters);
711 while ((fsm = silc_list_get(event->waiters))) {
713 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
718 p = silc_calloc(1, sizeof(*p));
719 if (silc_unlikely(!p))
723 silc_fsm_event_ref(event);
725 /* Signal through scheduler. Wake up destination scheduler in case
726 caller is a real thread. */
727 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
728 silc_schedule_wakeup(fsm->schedule);
731 silc_mutex_unlock(lock);
734 /* Post thread termination event. Special function used only to
735 signal thread termination when SILC_FSM_THREAD_WAIT was used. */
737 static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
740 SilcMutex lock = event->fsm->u.m.lock;
742 SILC_LOG_DEBUG(("Post thread terminate event %p", event));
744 silc_mutex_lock(lock);
746 silc_list_start(event->waiters);
747 while ((fsm = silc_list_get(event->waiters))) {
748 /* Signal on thread termination. Wake up destination scheduler in case
749 caller is a real thread. */
750 silc_list_del(event->waiters, fsm);
751 silc_fsm_continue(fsm);
752 silc_schedule_wakeup(fsm->schedule);
755 silc_mutex_unlock(lock);
760 void *silc_fsm_thread(void *context)
762 SilcFSM fsm = context;
763 SilcSchedule old = fsm->schedule;
765 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
767 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
768 cannot be used in this thread. Application may still use it if it
769 wants but we use our own. */
770 fsm->schedule = silc_schedule_init(0, old, silc_schedule_get_stack(old));
771 if (silc_unlikely(!fsm->schedule)) {
776 /* Start the FSM thread */
777 if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
778 silc_fsm_run, fsm, 0, 0))) {
779 silc_schedule_uninit(fsm->schedule);
784 /* Run the scheduler */
785 silc_schedule(fsm->schedule);
788 silc_schedule_uninit(fsm->schedule);
792 /* Finish the FSM thread in the main thread */
793 SILC_ASSERT(fsm->finished);
794 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
796 silc_schedule_wakeup(fsm->schedule);