5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2005 - 2006 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_sema_timedout);
25 SILC_TASK_CALLBACK(silc_fsm_start_real_thread);
26 static void *silc_fsm_thread(void *context);
27 static void silc_fsm_thread_termination_post(SilcFSMSema sema);
28 static void silc_fsm_sema_ref(SilcFSMSema sema);
29 static void silc_fsm_sema_unref(SilcFSMSema sema);
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));
44 if (!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;
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));
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;
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 && f->u.m.threads)
145 SILC_ASSERT(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.sema)
152 silc_fsm_sema_free(f->u.t.sema);
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);
218 /* Start FSM in the specified state synchronously */
220 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
224 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
227 f->next_state = start_state;
228 f->synchronous = TRUE;
231 /* Start real thread directly */
232 if (f->thread && f->real_thread) {
233 silc_fsm_start_real_thread(f->schedule,
234 silc_schedule_get_context(f->schedule),
239 /* Normal FSM operation */
240 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
243 /* Set next FSM state */
245 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
248 f->next_state = next_state;
251 /* Continue after timeout */
253 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
254 SilcUInt32 seconds, SilcUInt32 useconds)
257 f->next_state = next_state;
258 if (!seconds && !useconds)
260 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
262 f->next_later = TRUE;
265 /* Continue after callback or async operation */
267 void silc_fsm_continue(void *fsm)
271 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
272 f->next_later = FALSE;
274 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
275 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
278 /* Continue after callback or async operation immediately */
280 void silc_fsm_continue_sync(void *fsm)
284 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
285 f->next_later = FALSE;
287 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
292 void silc_fsm_finish(void *fsm)
296 SILC_ASSERT(!f->finished);
298 /* Machine must not have active threads */
299 if (!f->thread && f->u.m.threads)
300 assert(f->u.m.threads == 0);
305 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
306 f->next_later = FALSE;
308 /* If we are thread and using real threads, the FSM thread will finish
309 after the real thread has finished, in the main thread. */
310 if (f->thread && f->real_thread) {
311 /* Stop the real thread's scheduler to finish the thread */
312 silc_schedule_stop(f->schedule);
313 silc_schedule_wakeup(f->schedule);
317 /* Normal FSM operation */
319 if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm,
323 silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
327 /* Return associated scheduler */
329 SilcSchedule silc_fsm_get_schedule(void *fsm)
335 /* Return thread's machine */
337 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
339 SILC_ASSERT(thread->thread);
340 return (SilcFSM)thread->u.t.fsm;
343 /* Returns TRUE if FSM is started */
345 SilcBool silc_fsm_is_started(void *fsm)
353 void silc_fsm_set_context(void *fsm, void *fsm_context)
356 f->fsm_context = fsm_context;
361 void *silc_fsm_get_context(void *fsm)
364 return f->fsm_context;
367 /* Set state context */
369 void silc_fsm_set_state_context(void *fsm, void *state_context)
372 f->state_context = state_context;
375 /* Get state context */
377 void *silc_fsm_get_state_context(void *fsm)
380 return f->state_context;
383 /* Wait for thread to terminate */
385 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
389 SILC_ASSERT(t->thread);
391 t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
395 SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
396 silc_fsm_sema_wait(t->u.t.sema, fsm);
402 SILC_TASK_CALLBACK(silc_fsm_run)
404 SilcFSM fsm = context;
405 SilcFSMStatus status;
407 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
411 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
412 while (status == SILC_FSM_CONTINUE);
416 /* Continue through scheduler */
417 silc_fsm_continue(fsm);
421 /* The machine is in hold */
422 SILC_LOG_DEBUG(("State wait %p", fsm));
423 fsm->synchronous = FALSE;
426 case SILC_FSM_FINISH:
427 /* Finish the state machine */
428 SILC_LOG_DEBUG(("State finish %p", fsm));
429 silc_fsm_finish(fsm);
437 /* Finishes the FSM. This is always executed in the main thread, even
438 for FSM threads that were run in real threads. */
440 SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
442 SilcFSM fsm = context;
444 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
446 fsm->next_state = NULL;
449 /* This is thread, send signal */
451 silc_fsm_thread_termination_post(fsm->u.t.sema);
452 silc_fsm_sema_free(fsm->u.t.sema);
453 fsm->u.t.sema = NULL;
456 /* Remove the thread from machine */
457 fsm->u.t.fsm->u.m.threads--;
459 /* Call the destructor callback only if the underlaying machine is
461 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
462 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
466 silc_mutex_free(fsm->u.m.lock);
467 fsm->u.m.lock = NULL;
470 /* Call the destructor callback. */
472 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
476 /* Allocate FSM semaphore */
478 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
482 sema = silc_calloc(1, sizeof(*sema));
486 silc_fsm_sema_init(sema, fsm, value);
487 sema->allocated = TRUE;
492 /* Initializes FSM semaphore */
494 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
496 SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
497 SILC_ASSERT(!fsm->thread);
498 memset(sema, 0, sizeof(*sema));
501 silc_list_init(sema->waiters, struct SilcFSMObject, next);
507 void silc_fsm_sema_free(SilcFSMSema sema)
509 if (sema->refcnt > 0)
511 if (silc_list_count(sema->waiters) > 0)
516 /* Reference semaphore */
518 static void silc_fsm_sema_ref(SilcFSMSema sema)
523 /* Unreference semaphore */
525 static void silc_fsm_sema_unref(SilcFSMSema sema)
528 if (sema->refcnt == 0 && sema->allocated)
529 silc_fsm_sema_free(sema);
532 /* Wait until semaphore is non-zero. */
534 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
536 SilcMutex lock = sema->fsm->u.m.lock;
538 silc_mutex_lock(lock);
541 #if defined(SILC_DEBUG)
543 silc_list_start(sema->waiters);
544 while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
545 SILC_ASSERT(entry != fsm);
546 #endif /* SILC_DEBUG */
548 SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
550 /* Add the FSM to waiter list */
551 silc_list_add(sema->waiters, fsm);
552 silc_mutex_unlock(lock);
556 SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
558 /* It is possible that this FSM is in the list so remove it */
559 silc_list_del(sema->waiters, fsm);
561 silc_mutex_unlock(lock);
565 /* Wait util semaphore is non-zero, or timeout occurs. */
567 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
568 SilcUInt32 seconds, SilcUInt32 useconds,
571 SilcMutex lock = sema->fsm->u.m.lock;
575 silc_mutex_lock(lock);
577 if (f->sema_timedout) {
578 SILC_LOG_DEBUG(("Semaphore was timedout"));
579 f->sema_timedout = FALSE;
582 silc_mutex_unlock(lock);
586 silc_mutex_unlock(lock);
588 value = silc_fsm_sema_wait(sema, fsm);
590 silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
591 f, seconds, useconds);
601 /* Semaphore timedout */
603 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
605 SilcFSM fsm = context;
606 SilcMutex lock = fsm->sema->fsm->u.m.lock;
608 SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
610 /* Remove the waiter from the semaphore */
611 silc_mutex_lock(lock);
612 silc_list_del(fsm->sema->waiters, fsm);
616 silc_fsm_continue(fsm);
617 fsm->sema_timedout = TRUE;
621 silc_mutex_unlock(lock);
624 /* Signalled, semaphore */
626 SILC_TASK_CALLBACK(silc_fsm_signal)
628 SilcFSMSemaPost p = context;
629 SilcMutex lock = p->sema->fsm->u.m.lock;
631 /* If the semaphore value has went to zero while we've been waiting this
632 callback, sempahore has been been signalled already. It can happen
633 when using real threads because the FSM may not be waiting state when
634 the sempahore is posted. */
635 silc_mutex_lock(lock);
636 if (!p->sema->value) {
637 silc_mutex_unlock(lock);
638 silc_fsm_sema_unref(p->sema);
642 silc_mutex_unlock(lock);
644 SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
648 silc_fsm_continue_sync(p->fsm);
650 silc_fsm_sema_unref(p->sema);
654 /* Increase semaphore */
656 void silc_fsm_sema_post(SilcFSMSema sema)
660 SilcMutex lock = sema->fsm->u.m.lock;
662 SILC_LOG_DEBUG(("Posting semaphore %p", sema));
664 silc_mutex_lock(lock);
667 silc_list_start(sema->waiters);
668 while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
670 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
675 p = silc_calloc(1, sizeof(*p));
680 silc_fsm_sema_ref(sema);
682 /* Signal through scheduler. Wake up destination scheduler in case
683 caller is a real thread. */
684 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
685 silc_schedule_wakeup(fsm->schedule);
688 silc_mutex_unlock(lock);
691 /* Post thread termination semaphore. Special function used only to
692 signal thread termination when SILC_FSM_THREAD_WAIT was used. */
694 static void silc_fsm_thread_termination_post(SilcFSMSema sema)
697 SilcMutex lock = sema->fsm->u.m.lock;
699 SILC_LOG_DEBUG(("Post thread terminate semaphore %p", sema));
701 silc_mutex_lock(lock);
703 silc_list_start(sema->waiters);
704 while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
705 /* Signal on thread termination. Wake up destination scheduler in case
706 caller is a real thread. */
707 silc_list_del(sema->waiters, fsm);
708 silc_fsm_continue(fsm);
709 silc_schedule_wakeup(fsm->schedule);
712 silc_mutex_unlock(lock);
717 static void *silc_fsm_thread(void *context)
719 SilcFSM fsm = context;
720 SilcSchedule old = fsm->schedule;
722 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
724 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
725 cannot be used in this thread. Application may still use it if it
726 wants but we use our own. */
727 fsm->schedule = silc_schedule_init(0, old);
731 /* Start the FSM thread */
732 if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 0))
735 /* Run the scheduler */
736 silc_schedule(fsm->schedule);
739 silc_schedule_uninit(fsm->schedule);
743 /* Finish the FSM thread in the main thread */
744 SILC_ASSERT(fsm->finished);
745 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
747 silc_schedule_wakeup(fsm->schedule);