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;
77 /* Allocate FSM thread. Internally machine and thread use same context. */
79 SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
81 SilcFSMThreadDestructor destructor,
82 void *destructor_context,
87 thread = silc_calloc(1, sizeof(*thread));
91 silc_fsm_thread_init(thread, fsm, thread_context, destructor,
92 destructor_context, real_thread);
96 /* Initialize FSM thread. Internally machine and thread use same context. */
98 void silc_fsm_thread_init(SilcFSMThread thread,
100 void *thread_context,
101 SilcFSMThreadDestructor destructor,
102 void *destructor_context,
103 SilcBool real_thread)
105 SILC_LOG_DEBUG(("Initializing new thread %p (%s)",
106 thread, real_thread ? "real" : "FSM"));
108 SILC_ASSERT(!fsm->thread);
110 thread->fsm_context = thread_context;
111 thread->state_context = NULL;
112 thread->destructor = (SilcFSMDestructor)destructor;
113 thread->destructor_context = destructor_context;
114 thread->schedule = fsm->schedule;
115 thread->thread = TRUE;
116 thread->async_call = FALSE;
117 thread->real_thread = real_thread;
118 thread->u.t.fsm = fsm;
123 /* Allocate lock for the machine if using real threads. */
124 if (real_thread && !fsm->u.m.lock)
125 if (!silc_mutex_alloc(&fsm->u.m.lock))
126 thread->real_thread = FALSE;
129 /* FSM is destroyed through scheduler to make sure that all dying
130 real system threads will have their finish callbacks scheduled before
131 this one (when SILC_FSM_THREAD_WAIT was used). */
133 SILC_TASK_CALLBACK(silc_fsm_free_final)
137 #if defined(SILC_DEBUG)
138 /* We must be finished */
139 SILC_ASSERT(f->finished);
141 /* Machine must not have active threads */
142 if (!f->thread && f->u.m.threads)
143 SILC_ASSERT(f->u.m.threads == 0);
144 #endif /* SILC_DEBUG */
146 if (!f->thread && f->u.m.lock)
147 silc_mutex_free(f->u.m.lock);
149 if (f->thread && f->u.t.sema)
150 silc_fsm_sema_free(f->u.t.sema);
157 void silc_fsm_free(void *fsm)
160 silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final, f, 0, 0);
163 /* Task to start real thread. We start threads through scheduler, not
164 directly in silc_fsm_start. */
166 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
171 if (silc_thread_create(silc_fsm_thread, f, FALSE))
173 #endif /* SILC_THREADS */
175 SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
177 /* Normal FSM operation */
178 f->real_thread = FALSE;
179 silc_fsm_continue_sync(f);
182 /* Start FSM in the specified state */
184 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
188 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
191 f->next_state = start_state;
192 f->synchronous = FALSE;
194 /* Start real thread through scheduler */
195 if (f->thread && f->real_thread) {
196 silc_schedule_task_add_timeout(f->schedule, silc_fsm_start_real_thread,
201 /* Normal FSM operation */
202 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0);
205 /* Start FSM in the specified state synchronously */
207 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
211 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
214 f->next_state = start_state;
215 f->synchronous = TRUE;
217 /* Start real thread directly */
218 if (f->thread && f->real_thread) {
219 silc_fsm_start_real_thread(f->schedule,
220 silc_schedule_get_context(f->schedule),
225 /* Normal FSM operation */
226 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
229 /* Set next FSM state */
231 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
234 f->next_state = next_state;
237 /* Continue after timeout */
239 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
240 SilcUInt32 seconds, SilcUInt32 useconds)
243 f->next_state = next_state;
244 if (!seconds && !useconds)
246 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
248 f->next_later = TRUE;
251 /* Continue after callback or async operation */
253 void silc_fsm_continue(void *fsm)
257 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
258 f->next_later = FALSE;
260 if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
261 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
264 /* Continue after callback or async operation immediately */
266 void silc_fsm_continue_sync(void *fsm)
270 silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
271 f->next_later = FALSE;
273 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
278 void silc_fsm_finish(void *fsm)
282 SILC_ASSERT(!f->finished);
285 /* If we are thread and using real threads, the FSM thread will finish
286 after the real thread has finished, in the main thread. */
287 if (f->thread && f->real_thread) {
288 /* Stop the real thread's scheduler to finish the thread */
289 silc_schedule_stop(f->schedule);
290 silc_schedule_wakeup(f->schedule);
294 /* Normal FSM operation */
296 silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
299 silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm, f, 0, 0);
302 /* Return associated scheduler */
304 SilcSchedule silc_fsm_get_schedule(void *fsm)
310 /* Return thread's machine */
312 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
314 SILC_ASSERT(thread->thread);
315 return (SilcFSM)thread->u.t.fsm;
320 void silc_fsm_set_context(void *fsm, void *fsm_context)
323 f->fsm_context = fsm_context;
328 void *silc_fsm_get_context(void *fsm)
331 return f->fsm_context;
334 /* Set state context */
336 void silc_fsm_set_state_context(void *fsm, void *state_context)
339 f->state_context = state_context;
342 /* Get state context */
344 void *silc_fsm_get_state_context(void *fsm)
347 return f->state_context;
350 /* Wait for thread to terminate */
352 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
356 SILC_ASSERT(t->thread);
360 t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
364 SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
365 silc_fsm_sema_wait(t->u.t.sema, fsm);
371 SILC_TASK_CALLBACK(silc_fsm_run)
373 SilcFSM fsm = context;
374 SilcFSMStatus status;
376 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
380 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
381 while (status == SILC_FSM_CONTINUE);
385 /* Continue through scheduler */
386 silc_fsm_continue(fsm);
390 /* The machine is in hold */
391 SILC_LOG_DEBUG(("State wait %p", fsm));
392 fsm->synchronous = FALSE;
395 case SILC_FSM_FINISH:
396 /* Finish the state machine */
397 SILC_LOG_DEBUG(("State finish %p", fsm));
398 silc_fsm_finish(fsm);
406 /* Finishes the FSM. This is always executed in the main thread, even
407 for FSM threads that were run in real threads. */
409 SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
411 SilcFSM fsm = context;
413 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
415 fsm->next_state = NULL;
418 /* This is thread, send signal */
420 silc_fsm_thread_termination_post(fsm->u.t.sema);
421 silc_fsm_sema_free(fsm->u.t.sema);
422 fsm->u.t.sema = NULL;
425 /* Remove the thread from machine */
426 fsm->u.t.fsm->u.m.threads--;
428 /* Call the destructor callback only if the underlaying machine is
430 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
431 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
435 silc_mutex_free(fsm->u.m.lock);
436 fsm->u.m.lock = NULL;
439 /* Call the destructor callback. */
441 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
445 /* Allocate FSM semaphore */
447 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
451 sema = silc_calloc(1, sizeof(*sema));
455 silc_fsm_sema_init(sema, fsm, value);
456 sema->allocated = TRUE;
461 /* Initializes FSM semaphore */
463 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
465 SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
466 SILC_ASSERT(!fsm->thread);
467 memset(sema, 0, sizeof(*sema));
470 silc_list_init(sema->waiters, struct SilcFSMObject, next);
476 void silc_fsm_sema_free(SilcFSMSema sema)
478 if (sema->refcnt > 0)
480 if (silc_list_count(sema->waiters) > 0)
485 /* Reference semaphore */
487 static void silc_fsm_sema_ref(SilcFSMSema sema)
492 /* Unreference semaphore */
494 static void silc_fsm_sema_unref(SilcFSMSema sema)
497 if (sema->refcnt == 0 && sema->allocated)
498 silc_fsm_sema_free(sema);
501 /* Wait until semaphore is non-zero. */
503 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
505 SilcMutex lock = sema->fsm->u.m.lock;
507 silc_mutex_lock(lock);
510 #if defined(SILC_DEBUG)
512 silc_list_start(sema->waiters);
513 while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
514 SILC_ASSERT(entry != fsm);
515 #endif /* SILC_DEBUG */
517 SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
519 /* Add the FSM to waiter list */
520 silc_list_add(sema->waiters, fsm);
521 silc_mutex_unlock(lock);
525 SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
527 /* It is possible that this FSM is in the list so remove it */
528 silc_list_del(sema->waiters, fsm);
530 silc_mutex_unlock(lock);
534 /* Wait util semaphore is non-zero, or timeout occurs. */
536 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
537 SilcUInt32 seconds, SilcUInt32 useconds,
540 SilcMutex lock = sema->fsm->u.m.lock;
544 silc_mutex_lock(lock);
546 if (f->sema_timedout) {
547 SILC_LOG_DEBUG(("Semaphore was timedout"));
548 f->sema_timedout = FALSE;
551 silc_mutex_unlock(lock);
555 silc_mutex_unlock(lock);
557 value = silc_fsm_sema_wait(sema, fsm);
559 silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
560 f, seconds, useconds);
570 /* Semaphore timedout */
572 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
574 SilcFSM fsm = context;
575 SilcMutex lock = fsm->sema->fsm->u.m.lock;
577 SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
579 /* Remove the waiter from the semaphore */
580 silc_mutex_lock(lock);
581 silc_list_del(fsm->sema->waiters, fsm);
585 silc_fsm_continue(fsm);
586 fsm->sema_timedout = TRUE;
590 silc_mutex_unlock(lock);
593 /* Signalled, semaphore */
595 SILC_TASK_CALLBACK(silc_fsm_signal)
597 SilcFSMSemaPost p = context;
598 SilcMutex lock = p->sema->fsm->u.m.lock;
600 /* If the semaphore value has went to zero while we've been waiting this
601 callback, sempahore has been been signalled already. It can happen
602 when using real threads because the FSM may not be waiting state when
603 the sempahore is posted. */
604 silc_mutex_lock(lock);
605 if (!p->sema->value) {
606 silc_mutex_unlock(lock);
607 silc_fsm_sema_unref(p->sema);
611 silc_mutex_unlock(lock);
613 SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
617 silc_fsm_continue_sync(p->fsm);
619 silc_fsm_sema_unref(p->sema);
623 /* Increase semaphore */
625 void silc_fsm_sema_post(SilcFSMSema sema)
629 SilcMutex lock = sema->fsm->u.m.lock;
631 SILC_LOG_DEBUG(("Posting semaphore %p", sema));
633 silc_mutex_lock(lock);
636 silc_list_start(sema->waiters);
637 while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
639 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
644 p = silc_calloc(1, sizeof(*p));
649 silc_fsm_sema_ref(sema);
651 /* Signal through scheduler. Wake up destination scheduler in case
652 caller is a real thread. */
653 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
654 silc_schedule_wakeup(fsm->schedule);
657 silc_mutex_unlock(lock);
660 /* Post thread termination semaphore. Special function used only to
661 signal thread termination when SILC_FSM_THREAD_WAIT was used. */
663 static void silc_fsm_thread_termination_post(SilcFSMSema sema)
666 SilcMutex lock = sema->fsm->u.m.lock;
668 SILC_LOG_DEBUG(("Post thread terminate semaphore %p", sema));
670 silc_mutex_lock(lock);
672 silc_list_start(sema->waiters);
673 while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
674 /* Signal on thread termination. Wake up destination scheduler in case
675 caller is a real thread. */
676 silc_list_del(sema->waiters, fsm);
677 silc_fsm_continue(fsm);
678 silc_schedule_wakeup(fsm->schedule);
681 silc_mutex_unlock(lock);
686 static void *silc_fsm_thread(void *context)
688 SilcFSM fsm = context;
689 SilcSchedule old = fsm->schedule;
691 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
693 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
694 cannot be used in this thread. Application may still use it if it
695 wants but we use our own. */
696 fsm->schedule = silc_schedule_init(0, old);
700 /* Start the FSM thread */
701 if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 0))
704 /* Run the scheduler */
705 silc_schedule(fsm->schedule);
708 silc_schedule_uninit(fsm->schedule);
712 /* Finish the FSM thread in the main thread */
713 SILC_ASSERT(fsm->finished);
714 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
716 silc_schedule_wakeup(fsm->schedule);