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);
159 void silc_fsm_free(void *fsm)
163 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final,
166 silc_fsm_free_final(f->schedule, silc_schedule_get_context(f->schedule),
170 /* Task to start real thread. We start threads through scheduler, not
171 directly in silc_fsm_start. */
173 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
178 if (silc_thread_create(silc_fsm_thread, f, FALSE))
180 #endif /* SILC_THREADS */
182 SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
184 /* Normal FSM operation */
185 f->real_thread = FALSE;
186 silc_fsm_continue_sync(f);
189 /* Start FSM in the specified state */
191 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
195 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
198 f->next_state = start_state;
199 f->synchronous = FALSE;
202 /* Start real thread through scheduler */
203 if (f->thread && f->real_thread) {
204 if (!silc_schedule_task_add_timeout(f->schedule,
205 silc_fsm_start_real_thread,
207 silc_fsm_start_real_thread(f->schedule,
208 silc_schedule_get_context(f->schedule),
213 /* Normal FSM operation */
214 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
215 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
217 /* Wakeup scheduler in case we are starting this thread from another
220 silc_schedule_wakeup(f->schedule);
223 /* Start FSM in the specified state synchronously */
225 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
229 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
232 f->next_state = start_state;
233 f->synchronous = TRUE;
236 /* Start real thread directly */
237 if (f->thread && f->real_thread) {
238 silc_fsm_start_real_thread(f->schedule,
239 silc_schedule_get_context(f->schedule),
244 /* Normal FSM operation */
245 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
248 /* Set next FSM state */
250 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
253 f->next_state = next_state;
256 /* Continue after timeout */
258 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
259 SilcUInt32 seconds, SilcUInt32 useconds)
262 f->next_state = next_state;
263 if (!seconds && !useconds)
265 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
267 f->next_later = TRUE;
270 /* Continue after callback or async operation */
272 void silc_fsm_continue(void *fsm)
276 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
277 f->next_later = FALSE;
279 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
280 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
283 /* Continue after callback or async operation immediately */
285 void silc_fsm_continue_sync(void *fsm)
289 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
290 f->next_later = FALSE;
292 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
297 void silc_fsm_finish(void *fsm)
301 SILC_ASSERT(!f->finished);
303 /* Machine must not have active threads */
304 if (!f->thread && silc_atomic_get_int32(&f->u.m.threads))
305 assert(silc_atomic_get_int32(&f->u.m.threads) == 0);
310 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
311 f->next_later = FALSE;
313 /* If we are thread and using real threads, the FSM thread will finish
314 after the real thread has finished, in the main thread. */
315 if (f->thread && f->real_thread) {
316 /* Stop the real thread's scheduler to finish the thread */
317 silc_schedule_stop(f->schedule);
318 silc_schedule_wakeup(f->schedule);
322 /* Normal FSM operation */
324 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm,
328 silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
332 /* Return associated scheduler */
334 SilcSchedule silc_fsm_get_schedule(void *fsm)
340 /* Return thread's machine */
342 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
344 SILC_ASSERT(thread->thread);
345 return (SilcFSM)thread->u.t.fsm;
348 /* Returns TRUE if FSM is started */
350 SilcBool silc_fsm_is_started(void *fsm)
358 void silc_fsm_set_context(void *fsm, void *fsm_context)
361 f->fsm_context = fsm_context;
366 void *silc_fsm_get_context(void *fsm)
369 return f->fsm_context;
372 /* Set state context */
374 void silc_fsm_set_state_context(void *fsm, void *state_context)
377 f->state_context = state_context;
380 /* Get state context */
382 void *silc_fsm_get_state_context(void *fsm)
385 return f->state_context;
388 /* Wait for thread to terminate */
390 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
394 SILC_ASSERT(t->thread);
396 t->u.t.event = silc_fsm_event_alloc(t->u.t.fsm);
400 SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
401 silc_fsm_event_wait(t->u.t.event, fsm);
407 SILC_TASK_CALLBACK(silc_fsm_run)
409 SilcFSM fsm = context;
410 SilcFSMStatus status;
412 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
416 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
417 while (status == SILC_FSM_ST_CONTINUE);
420 case SILC_FSM_ST_YIELD:
421 /* Continue through scheduler */
422 silc_fsm_continue(fsm);
425 case SILC_FSM_ST_WAIT:
426 /* The machine is in hold */
427 SILC_LOG_DEBUG(("State wait %p", fsm));
428 fsm->synchronous = FALSE;
431 case SILC_FSM_ST_FINISH:
432 /* Finish the state machine */
433 SILC_LOG_DEBUG(("State finish %p", fsm));
434 silc_fsm_finish(fsm);
442 /* Finishes the FSM. This is always executed in the main thread, even
443 for FSM threads that were run in real threads. */
445 SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
447 SilcFSM fsm = context;
449 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
451 fsm->next_state = NULL;
454 /* This is thread, send signal */
455 if (fsm->u.t.event) {
456 silc_fsm_thread_termination_signal(fsm->u.t.event);
457 silc_fsm_event_free(fsm->u.t.event);
458 fsm->u.t.event = NULL;
461 /* Remove the thread from machine */
462 silc_atomic_sub_int32(&fsm->u.t.fsm->u.m.threads, 1);
464 /* Call the destructor callback only if the underlaying machine is
466 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
467 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
471 silc_mutex_free(fsm->u.m.lock);
472 fsm->u.m.lock = NULL;
474 silc_atomic_uninit32(&fsm->u.m.threads);
476 /* Call the destructor callback. */
478 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
482 /* Allocate FSM event */
484 SilcFSMEvent silc_fsm_event_alloc(SilcFSM fsm)
488 event = silc_calloc(1, sizeof(*event));
489 if (silc_unlikely(!event))
492 silc_fsm_event_init(event, fsm);
493 event->allocated = TRUE;
498 /* Initializes FSM event */
500 void silc_fsm_event_init(SilcFSMEvent event, SilcFSM fsm)
502 SILC_LOG_DEBUG(("Initializing event %p", event));
503 SILC_ASSERT(!fsm->thread);
504 memset(event, 0, sizeof(*event));
507 silc_list_init(event->waiters, struct SilcFSMObject, next);
512 void silc_fsm_event_free(SilcFSMEvent event)
514 if (event->refcnt > 0)
516 if (silc_list_count(event->waiters) > 0)
521 /* Reference event */
523 static void silc_fsm_event_ref(SilcFSMEvent event)
528 /* Unreference event */
530 static void silc_fsm_event_unref(SilcFSMEvent event)
533 if (event->refcnt == 0 && event->allocated)
534 silc_fsm_event_free(event);
537 /* Wait until event is non-zero. */
539 SilcUInt32 silc_fsm_event_wait(SilcFSMEvent event, void *fsm)
541 SilcMutex lock = event->fsm->u.m.lock;
543 silc_mutex_lock(lock);
546 #if defined(SILC_DEBUG)
548 silc_list_start(event->waiters);
549 while ((entry = silc_list_get(event->waiters)))
550 SILC_ASSERT(entry != fsm);
551 #endif /* SILC_DEBUG */
553 SILC_LOG_DEBUG(("Waiting for event %p", event));
555 /* Add the FSM to waiter list */
556 silc_list_add(event->waiters, fsm);
557 silc_mutex_unlock(lock);
561 SILC_LOG_DEBUG(("Received event %p", event));
563 /* Remove from waiting */
564 silc_list_del(event->waiters, fsm);
566 /* Decrease the counter only after all waiters have acquired the signal. */
567 if (!silc_list_count(event->waiters))
570 silc_mutex_unlock(lock);
574 /* Wait util event is non-zero, or timeout occurs. */
576 SilcUInt32 silc_fsm_event_timedwait(SilcFSMEvent event, void *fsm,
577 SilcUInt32 seconds, SilcUInt32 useconds,
580 SilcMutex lock = event->fsm->u.m.lock;
584 silc_mutex_lock(lock);
586 if (f->event_timedout) {
587 SILC_LOG_DEBUG(("Event waiting timedout"));
588 f->event_timedout = FALSE;
591 silc_mutex_unlock(lock);
595 silc_mutex_unlock(lock);
597 value = silc_fsm_event_wait(event, fsm);
599 silc_schedule_task_add_timeout(f->schedule, silc_fsm_event_timedout,
600 f, seconds, useconds);
612 SILC_TASK_CALLBACK(silc_fsm_event_timedout)
614 SilcFSM fsm = context;
615 SilcMutex lock = fsm->event->fsm->u.m.lock;
617 SILC_LOG_DEBUG(("Event %p timedout", fsm->event));
619 /* Remove the waiter from the event waiters list */
620 silc_mutex_lock(lock);
621 silc_list_del(fsm->event->waiters, fsm);
625 silc_fsm_continue(fsm);
626 fsm->event_timedout = TRUE;
630 silc_mutex_unlock(lock);
633 /* Signalled, event */
635 SILC_TASK_CALLBACK(silc_fsm_signal)
637 SilcFSMEventSignal p = context;
638 SilcMutex lock = p->event->fsm->u.m.lock;
641 /* We have to check for couple of things before delivering the signal. */
643 /* If the event value has went to zero while we've been waiting this
644 callback, the event has been been signalled already. It can happen
645 when using real threads because the FSM may not be in waiting state
646 when the event is signalled. */
647 silc_mutex_lock(lock);
648 if (!p->event->value) {
649 silc_mutex_unlock(lock);
650 silc_fsm_event_unref(p->event);
655 /* If the waiter is not waiting anymore, don't deliver the signal. It
656 can happen if there were multiple signallers and the waiter went away
657 after the first signal. */
658 silc_list_start(p->event->waiters);
659 while ((fsm = silc_list_get(p->event->waiters)))
663 silc_mutex_unlock(lock);
664 silc_fsm_event_unref(p->event);
668 silc_mutex_unlock(lock);
670 SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
674 silc_fsm_continue_sync(p->fsm);
676 silc_fsm_event_unref(p->event);
682 void silc_fsm_event_signal(SilcFSMEvent event)
685 SilcFSMEventSignal p;
686 SilcMutex lock = event->fsm->u.m.lock;
688 SILC_LOG_DEBUG(("Signal event %p", event));
690 silc_mutex_lock(lock);
693 silc_list_start(event->waiters);
694 while ((fsm = silc_list_get(event->waiters))) {
696 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
701 p = silc_calloc(1, sizeof(*p));
702 if (silc_unlikely(!p))
706 silc_fsm_event_ref(event);
708 /* Signal through scheduler. Wake up destination scheduler in case
709 caller is a real thread. */
710 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
711 silc_schedule_wakeup(fsm->schedule);
714 silc_mutex_unlock(lock);
717 /* Post thread termination event. Special function used only to
718 signal thread termination when SILC_FSM_THREAD_WAIT was used. */
720 static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
723 SilcMutex lock = event->fsm->u.m.lock;
725 SILC_LOG_DEBUG(("Post thread terminate event %p", event));
727 silc_mutex_lock(lock);
729 silc_list_start(event->waiters);
730 while ((fsm = silc_list_get(event->waiters))) {
731 /* Signal on thread termination. Wake up destination scheduler in case
732 caller is a real thread. */
733 silc_list_del(event->waiters, fsm);
734 silc_fsm_continue(fsm);
735 silc_schedule_wakeup(fsm->schedule);
738 silc_mutex_unlock(lock);
743 void *silc_fsm_thread(void *context)
745 SilcFSM fsm = context;
746 SilcSchedule old = fsm->schedule;
748 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
750 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
751 cannot be used in this thread. Application may still use it if it
752 wants but we use our own. */
753 fsm->schedule = silc_schedule_init(0, old);
754 if (silc_unlikely(!fsm->schedule))
757 /* Start the FSM thread */
758 if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
759 silc_fsm_run, fsm, 0, 0)))
762 /* Run the scheduler */
763 silc_schedule(fsm->schedule);
766 silc_schedule_uninit(fsm->schedule);
770 /* Finish the FSM thread in the main thread */
771 SILC_ASSERT(fsm->finished);
772 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
774 silc_schedule_wakeup(fsm->schedule);