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);
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, 1);
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, 1);
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, 1))
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);
276 /* Return associated scheduler */
278 SilcSchedule silc_fsm_get_schedule(void *fsm)
284 /* Return thread's machine */
286 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
288 SILC_ASSERT(thread->thread);
289 return (SilcFSM)thread->u.t.fsm;
294 void silc_fsm_set_context(void *fsm, void *fsm_context)
297 f->fsm_context = fsm_context;
302 void *silc_fsm_get_context(void *fsm)
305 return f->fsm_context;
308 /* Set state context */
310 void silc_fsm_set_state_context(void *fsm, void *state_context)
313 f->state_context = state_context;
316 /* Get state context */
318 void *silc_fsm_get_state_context(void *fsm)
321 return f->state_context;
324 /* Wait for thread to terminate */
326 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
330 SILC_ASSERT(t->thread);
334 t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
338 SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
339 silc_fsm_sema_wait(t->u.t.sema, fsm);
345 SILC_TASK_CALLBACK(silc_fsm_run)
347 SilcFSM fsm = context;
348 SilcFSMStatus status;
350 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
354 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
355 while (status == SILC_FSM_CONTINUE);
359 /* Continue through scheduler */
360 silc_fsm_continue(fsm);
364 /* The machine is in hold */
365 SILC_LOG_DEBUG(("State wait %p", fsm));
366 fsm->synchronous = FALSE;
369 case SILC_FSM_FINISH:
370 /* Finish the state machine */
371 SILC_LOG_DEBUG(("State finish %p", fsm));
372 #if defined(SILC_DEBUG)
373 SILC_ASSERT(!fsm->finished);
374 #endif /* SILC_DEBUG */
375 fsm->finished = TRUE;
377 /* If we are thread and using real threads, the FSM thread will finish
378 after the real thread has finished, in the main thread. */
379 if (fsm->thread && fsm->real_thread) {
380 silc_schedule_stop(fsm->schedule);
384 /* Normal FSM operation */
385 if (fsm->synchronous)
386 silc_fsm_finish(fsm->schedule, app_context, 0, 0, fsm);
388 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish,
397 /* Finishes the FSM. This is always executed in the main thread, even
398 for FSM threads that were run in real threads. */
400 SILC_TASK_CALLBACK(silc_fsm_finish)
402 SilcFSM fsm = context;
404 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
406 fsm->next_state = NULL;
409 /* This is thread, send signal */
411 silc_fsm_thread_termination_post(fsm->u.t.sema);
412 silc_fsm_sema_free(fsm->u.t.sema);
413 fsm->u.t.sema = NULL;
416 /* Remove the thread from machine */
417 fsm->u.t.fsm->u.m.threads--;
419 /* Call the destructor callback only if the underlaying machine is
421 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
422 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
426 silc_mutex_free(fsm->u.m.lock);
427 fsm->u.m.lock = NULL;
430 /* Call the destructor callback. */
432 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
436 /* Allocate FSM semaphore */
438 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
442 sema = silc_calloc(1, sizeof(*sema));
446 silc_fsm_sema_init(sema, fsm, value);
447 sema->allocated = TRUE;
452 /* Initializes FSM semaphore */
454 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
456 SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
457 #if defined(SILC_DEBUG)
458 SILC_ASSERT(!fsm->thread);
459 #endif /* SILC_DEBUG */
460 memset(sema, 0, sizeof(*sema));
463 silc_list_init(sema->waiters, struct SilcFSMObject, next);
469 void silc_fsm_sema_free(SilcFSMSema sema)
471 if (sema->refcnt > 0)
473 if (silc_list_count(sema->waiters) > 0)
478 /* Reference semaphore */
480 static void silc_fsm_sema_ref(SilcFSMSema sema)
485 /* Unreference semaphore */
487 static void silc_fsm_sema_unref(SilcFSMSema sema)
490 if (sema->refcnt == 0 && sema->allocated)
491 silc_fsm_sema_free(sema);
494 /* Wait until semaphore is non-zero. */
496 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
498 SilcMutex lock = sema->fsm->u.m.lock;
500 silc_mutex_lock(lock);
503 #if defined(SILC_DEBUG)
505 silc_list_start(sema->waiters);
506 while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
507 SILC_ASSERT(entry != fsm);
508 #endif /* SILC_DEBUG */
510 SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
512 /* Add the FSM to waiter list */
513 silc_list_add(sema->waiters, fsm);
514 silc_mutex_unlock(lock);
518 SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
520 /* It is possible that this FSM is in the list so remove it */
521 silc_list_del(sema->waiters, fsm);
523 silc_mutex_unlock(lock);
527 /* Wait util semaphore is non-zero, or timeout occurs. */
529 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
530 SilcUInt32 seconds, SilcUInt32 useconds,
533 SilcMutex lock = sema->fsm->u.m.lock;
537 silc_mutex_lock(lock);
539 if (f->sema_timedout) {
540 SILC_LOG_DEBUG(("Semaphore was timedout"));
541 f->sema_timedout = FALSE;
544 silc_mutex_unlock(lock);
548 silc_mutex_unlock(lock);
550 value = silc_fsm_sema_wait(sema, fsm);
552 silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
553 f, seconds, useconds);
563 /* Semaphore timedout */
565 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
567 SilcFSM fsm = context;
568 SilcMutex lock = fsm->sema->fsm->u.m.lock;
570 SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
572 /* Remove the waiter from the semaphore */
573 silc_mutex_lock(lock);
574 silc_list_del(fsm->sema->waiters, fsm);
578 silc_fsm_continue(fsm);
579 fsm->sema_timedout = TRUE;
583 silc_mutex_unlock(lock);
586 /* Signalled, semaphore */
588 SILC_TASK_CALLBACK(silc_fsm_signal)
590 SilcFSMSemaPost p = context;
591 SilcMutex lock = p->sema->fsm->u.m.lock;
593 /* If the semaphore value has went to zero while we've been waiting this
594 callback, sempahore has been been signalled already. It can happen
595 when using real threads because the FSM may not be waiting state when
596 the sempahore is posted. */
597 silc_mutex_lock(lock);
598 if (!p->sema->value) {
599 silc_mutex_unlock(lock);
600 silc_fsm_sema_unref(p->sema);
604 silc_mutex_unlock(lock);
606 SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
610 silc_fsm_continue_sync(p->fsm);
612 silc_fsm_sema_unref(p->sema);
616 /* Increase semaphore */
618 void silc_fsm_sema_post(SilcFSMSema sema)
622 SilcMutex lock = sema->fsm->u.m.lock;
624 SILC_LOG_DEBUG(("Posting semaphore %p", sema));
626 silc_mutex_lock(lock);
629 silc_list_start(sema->waiters);
630 while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
632 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
637 p = silc_calloc(1, sizeof(*p));
642 silc_fsm_sema_ref(sema);
644 /* Signal through scheduler. Wake up destination scheduler in case
645 caller is a real thread. */
646 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 1);
647 silc_schedule_wakeup(fsm->schedule);
650 silc_mutex_unlock(lock);
653 /* Post thread termination semaphore. Special function used only to
654 signal thread termination when SILC_FSM_THREAD_WAIT was used. */
656 static void silc_fsm_thread_termination_post(SilcFSMSema sema)
659 SilcMutex lock = sema->fsm->u.m.lock;
661 SILC_LOG_DEBUG(("Post thread terminate semaphore %p", sema));
663 silc_mutex_lock(lock);
665 silc_list_start(sema->waiters);
666 while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
667 /* Signal on thread termination. Wake up destination scheduler in case
668 caller is a real thread. */
669 silc_list_del(sema->waiters, fsm);
670 silc_fsm_continue(fsm);
671 silc_schedule_wakeup(fsm->schedule);
674 silc_mutex_unlock(lock);
679 static void *silc_fsm_thread(void *context)
681 SilcFSM fsm = context;
682 SilcSchedule old = fsm->schedule;
684 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
686 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
687 cannot be used in this thread. Application may still use it if it
688 wants but we use our own. */
689 fsm->schedule = silc_schedule_init(0, old);
693 /* Start the FSM thread */
694 if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 1))
697 /* Run the scheduler */
698 silc_schedule(fsm->schedule);
701 silc_schedule_uninit(fsm->schedule);
705 /* Finish the FSM thread in the main thread */
706 SILC_ASSERT(fsm->finished);
707 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish, fsm, 0, 1);
708 silc_schedule_wakeup(fsm->schedule);