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),
210 silc_schedule_wakeup(f->schedule);
214 /* Normal FSM operation */
215 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
216 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
218 /* Wakeup scheduler in case we are starting this thread from another
221 silc_schedule_wakeup(f->schedule);
224 /* Start FSM in the specified state synchronously */
226 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
230 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
233 f->next_state = start_state;
234 f->synchronous = TRUE;
237 /* Start real thread directly */
238 if (f->thread && f->real_thread) {
239 silc_fsm_start_real_thread(f->schedule,
240 silc_schedule_get_context(f->schedule),
245 /* Normal FSM operation */
246 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
249 /* Set next FSM state */
251 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
254 f->next_state = next_state;
257 /* Continue after timeout */
259 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
260 SilcUInt32 seconds, SilcUInt32 useconds)
264 f->next_state = next_state;
265 if (!seconds && !useconds)
268 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
270 f->next_later = TRUE;
272 /* Wakeup up the scheduler just in case this was called from another
274 silc_schedule_wakeup(f->schedule);
277 /* Continue after callback or async operation */
279 void silc_fsm_continue(void *fsm)
284 /* Cancel next_later timeout */
285 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
286 f->next_later = FALSE;
289 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
290 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
292 /* Wakeup up the scheduler just in case this was called from another
294 silc_schedule_wakeup(f->schedule);
297 /* Continue after callback or async operation immediately */
299 void silc_fsm_continue_sync(void *fsm)
303 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
304 f->next_later = FALSE;
306 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
311 void silc_fsm_finish(void *fsm)
315 SILC_ASSERT(!f->finished);
317 /* Machine must not have active threads */
318 if (!f->thread && silc_atomic_get_int32(&f->u.m.threads))
319 assert(silc_atomic_get_int32(&f->u.m.threads) == 0);
324 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
325 f->next_later = FALSE;
327 /* If we are thread and using real threads, the FSM thread will finish
328 after the real thread has finished, in the main thread. */
329 if (f->thread && f->real_thread) {
330 /* Stop the real thread's scheduler to finish the thread */
331 silc_schedule_stop(f->schedule);
332 silc_schedule_wakeup(f->schedule);
336 /* Normal FSM operation */
338 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm,
342 silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
346 /* Return associated scheduler */
348 SilcSchedule silc_fsm_get_schedule(void *fsm)
354 /* Return thread's machine */
356 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
358 SILC_ASSERT(thread->thread);
359 return (SilcFSM)thread->u.t.fsm;
362 /* Returns TRUE if FSM is started */
364 SilcBool silc_fsm_is_started(void *fsm)
372 void silc_fsm_set_context(void *fsm, void *fsm_context)
375 f->fsm_context = fsm_context;
380 void *silc_fsm_get_context(void *fsm)
383 return f->fsm_context;
386 /* Set state context */
388 void silc_fsm_set_state_context(void *fsm, void *state_context)
391 f->state_context = state_context;
394 /* Get state context */
396 void *silc_fsm_get_state_context(void *fsm)
399 return f->state_context;
402 /* Wait for thread to terminate */
404 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
408 SILC_ASSERT(t->thread);
410 t->u.t.event = silc_fsm_event_alloc(t->u.t.fsm);
414 SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
415 silc_fsm_event_wait(t->u.t.event, fsm);
421 SILC_TASK_CALLBACK(silc_fsm_run)
423 SilcFSM fsm = context;
424 SilcFSMStatus status;
426 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
430 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
431 while (status == SILC_FSM_ST_CONTINUE);
434 case SILC_FSM_ST_YIELD:
435 /* Continue through scheduler */
436 silc_fsm_continue(fsm);
439 case SILC_FSM_ST_WAIT:
440 /* The machine is in hold */
441 SILC_LOG_DEBUG(("State wait %p", fsm));
442 fsm->synchronous = FALSE;
445 case SILC_FSM_ST_FINISH:
446 /* Finish the state machine */
447 SILC_LOG_DEBUG(("State finish %p", fsm));
448 silc_fsm_finish(fsm);
456 /* Finishes the FSM. This is always executed in the main thread, even
457 for FSM threads that were run in real threads. */
459 SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
461 SilcFSM fsm = context;
463 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
465 fsm->next_state = NULL;
468 /* This is thread, send signal */
469 if (fsm->u.t.event) {
470 silc_fsm_thread_termination_signal(fsm->u.t.event);
471 silc_fsm_event_free(fsm->u.t.event);
472 fsm->u.t.event = NULL;
475 /* Remove the thread from machine */
476 silc_atomic_sub_int32(&fsm->u.t.fsm->u.m.threads, 1);
478 /* Call the destructor callback only if the underlaying machine is
480 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
481 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
485 silc_mutex_free(fsm->u.m.lock);
486 fsm->u.m.lock = NULL;
488 silc_atomic_uninit32(&fsm->u.m.threads);
490 /* Call the destructor callback. */
492 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
496 /* Allocate FSM event */
498 SilcFSMEvent silc_fsm_event_alloc(SilcFSM fsm)
502 event = silc_calloc(1, sizeof(*event));
503 if (silc_unlikely(!event))
506 silc_fsm_event_init(event, fsm);
507 event->allocated = TRUE;
512 /* Initializes FSM event */
514 void silc_fsm_event_init(SilcFSMEvent event, SilcFSM fsm)
516 SILC_LOG_DEBUG(("Initializing event %p", event));
517 SILC_ASSERT(!fsm->thread);
518 memset(event, 0, sizeof(*event));
521 silc_list_init(event->waiters, struct SilcFSMObject, next);
526 void silc_fsm_event_free(SilcFSMEvent event)
528 if (event->refcnt > 0)
530 if (silc_list_count(event->waiters) > 0)
535 /* Reference event */
537 static void silc_fsm_event_ref(SilcFSMEvent event)
542 /* Unreference event */
544 static void silc_fsm_event_unref(SilcFSMEvent event)
547 if (event->refcnt == 0 && event->allocated)
548 silc_fsm_event_free(event);
551 /* Wait until event is non-zero. */
553 SilcUInt32 silc_fsm_event_wait(SilcFSMEvent event, void *fsm)
555 SilcMutex lock = event->fsm->u.m.lock;
557 silc_mutex_lock(lock);
560 #if defined(SILC_DEBUG)
562 silc_list_start(event->waiters);
563 while ((entry = silc_list_get(event->waiters)))
564 SILC_ASSERT(entry != fsm);
565 #endif /* SILC_DEBUG */
567 SILC_LOG_DEBUG(("Waiting for event %p", event));
569 /* Add the FSM to waiter list */
570 silc_list_add(event->waiters, fsm);
571 silc_mutex_unlock(lock);
575 SILC_LOG_DEBUG(("Received event %p", event));
577 /* Remove from waiting */
578 silc_list_del(event->waiters, fsm);
580 /* Decrease the counter only after all waiters have acquired the signal. */
581 if (!silc_list_count(event->waiters))
584 silc_mutex_unlock(lock);
588 /* Wait util event is non-zero, or timeout occurs. */
590 SilcUInt32 silc_fsm_event_timedwait(SilcFSMEvent event, void *fsm,
591 SilcUInt32 seconds, SilcUInt32 useconds,
594 SilcMutex lock = event->fsm->u.m.lock;
598 silc_mutex_lock(lock);
600 if (f->event_timedout) {
601 SILC_LOG_DEBUG(("Event waiting timedout"));
602 f->event_timedout = FALSE;
605 silc_mutex_unlock(lock);
609 silc_mutex_unlock(lock);
611 value = silc_fsm_event_wait(event, fsm);
613 silc_schedule_task_add_timeout(f->schedule, silc_fsm_event_timedout,
614 f, seconds, useconds);
626 SILC_TASK_CALLBACK(silc_fsm_event_timedout)
628 SilcFSM fsm = context;
629 SilcMutex lock = fsm->event->fsm->u.m.lock;
631 SILC_LOG_DEBUG(("Event %p timedout", fsm->event));
633 /* Remove the waiter from the event waiters list */
634 silc_mutex_lock(lock);
635 silc_list_del(fsm->event->waiters, fsm);
639 silc_fsm_continue(fsm);
640 fsm->event_timedout = TRUE;
644 silc_mutex_unlock(lock);
647 /* Signalled, event */
649 SILC_TASK_CALLBACK(silc_fsm_signal)
651 SilcFSMEventSignal p = context;
652 SilcMutex lock = p->event->fsm->u.m.lock;
655 /* We have to check for couple of things before delivering the signal. */
657 /* If the event value has went to zero while we've been waiting this
658 callback, the event has been been signalled already. It can happen
659 when using real threads because the FSM may not be in waiting state
660 when the event is signalled. */
661 silc_mutex_lock(lock);
662 if (!p->event->value) {
663 silc_mutex_unlock(lock);
664 silc_fsm_event_unref(p->event);
669 /* If the waiter is not waiting anymore, don't deliver the signal. It
670 can happen if there were multiple signallers and the waiter went away
671 after the first signal. */
672 silc_list_start(p->event->waiters);
673 while ((fsm = silc_list_get(p->event->waiters)))
677 silc_mutex_unlock(lock);
678 silc_fsm_event_unref(p->event);
682 silc_mutex_unlock(lock);
684 SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
688 silc_fsm_continue_sync(p->fsm);
690 silc_fsm_event_unref(p->event);
696 void silc_fsm_event_signal(SilcFSMEvent event)
699 SilcFSMEventSignal p;
700 SilcMutex lock = event->fsm->u.m.lock;
702 SILC_LOG_DEBUG(("Signal event %p", event));
704 silc_mutex_lock(lock);
707 silc_list_start(event->waiters);
708 while ((fsm = silc_list_get(event->waiters))) {
710 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
715 p = silc_calloc(1, sizeof(*p));
716 if (silc_unlikely(!p))
720 silc_fsm_event_ref(event);
722 /* Signal through scheduler. Wake up destination scheduler in case
723 caller is a real thread. */
724 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
725 silc_schedule_wakeup(fsm->schedule);
728 silc_mutex_unlock(lock);
731 /* Post thread termination event. Special function used only to
732 signal thread termination when SILC_FSM_THREAD_WAIT was used. */
734 static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
737 SilcMutex lock = event->fsm->u.m.lock;
739 SILC_LOG_DEBUG(("Post thread terminate event %p", event));
741 silc_mutex_lock(lock);
743 silc_list_start(event->waiters);
744 while ((fsm = silc_list_get(event->waiters))) {
745 /* Signal on thread termination. Wake up destination scheduler in case
746 caller is a real thread. */
747 silc_list_del(event->waiters, fsm);
748 silc_fsm_continue(fsm);
749 silc_schedule_wakeup(fsm->schedule);
752 silc_mutex_unlock(lock);
757 void *silc_fsm_thread(void *context)
759 SilcFSM fsm = context;
760 SilcSchedule old = fsm->schedule;
762 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
764 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
765 cannot be used in this thread. Application may still use it if it
766 wants but we use our own. */
767 fsm->schedule = silc_schedule_init(0, old);
768 if (silc_unlikely(!fsm->schedule))
771 /* Start the FSM thread */
772 if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
773 silc_fsm_run, fsm, 0, 0)))
776 /* Run the scheduler */
777 silc_schedule(fsm->schedule);
780 silc_schedule_uninit(fsm->schedule);
784 /* Finish the FSM thread in the main thread */
785 SILC_ASSERT(fsm->finished);
786 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
788 silc_schedule_wakeup(fsm->schedule);