5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2005 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 #if defined(SILC_DEBUG)
109 assert(!fsm->thread);
110 #endif /* SILC_DEBUG */
112 thread->fsm_context = thread_context;
113 thread->state_context = NULL;
114 thread->destructor = (SilcFSMDestructor)destructor;
115 thread->destructor_context = destructor_context;
116 thread->schedule = fsm->schedule;
117 thread->thread = TRUE;
118 thread->async_call = 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 /* Machine must not have active threads */
141 if (!f->thread && f->u.m.threads)
142 assert(f->u.m.threads == 0);
143 #endif /* SILC_DEBUG */
145 if (!f->thread && f->u.m.lock)
146 silc_mutex_free(f->u.m.lock);
148 if (f->thread && f->u.t.sema)
149 silc_fsm_sema_free(f->u.t.sema);
156 void silc_fsm_free(void *fsm)
159 silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final, f, 0, 1);
162 /* FSM is uninitialized through scheduler to make sure that all dying
163 real system threads will have their finish callbacks scheduled before
164 this one (when SILC_FSM_THREAD_WAIT was used). */
166 SILC_TASK_CALLBACK(silc_fsm_uninit_final)
170 #if defined(SILC_DEBUG)
171 /* Machine must not have active threads */
172 if (!f->thread && f->u.m.threads)
173 assert(f->u.m.threads == 0);
174 #endif /* SILC_DEBUG */
176 if (!f->thread && f->u.m.lock)
177 silc_mutex_free(f->u.m.lock);
179 if (f->thread && f->u.t.sema)
180 silc_fsm_sema_free(f->u.t.sema);
183 /* Uninitializes FSM */
185 void silc_fsm_uninit(void *fsm)
188 silc_schedule_task_add_timeout(f->schedule, silc_fsm_uninit_final, f, 0, 1);
191 /* Task to start real thread. We start threads through scheduler, not
192 directly in silc_fsm_start. */
194 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
199 if (silc_thread_create(silc_fsm_thread, f, FALSE))
201 #endif /* SILC_THREADS */
203 SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
205 /* Normal FSM operation */
206 f->real_thread = FALSE;
207 silc_fsm_continue_sync(f);
210 /* Start FSM in the specified state */
212 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
216 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
219 f->next_state = start_state;
220 f->synchronous = FALSE;
222 /* Start real thread through scheduler */
223 if (f->thread && f->real_thread) {
224 silc_schedule_task_add_timeout(f->schedule, silc_fsm_start_real_thread,
229 /* Normal FSM operation */
230 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
233 /* Start FSM in the specified state synchronously */
235 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
239 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
242 f->next_state = start_state;
243 f->synchronous = TRUE;
245 /* Start real thread directly */
246 if (f->thread && f->real_thread) {
247 silc_fsm_start_real_thread(f->schedule,
248 silc_schedule_get_context(f->schedule),
253 /* Normal FSM operation */
254 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
257 /* Set next FSM state */
259 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
262 f->next_state = next_state;
265 /* Continue after timeout */
267 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
268 SilcUInt32 seconds, SilcUInt32 useconds)
271 f->next_state = next_state;
272 if (!seconds && !useconds)
274 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
278 /* Continue after callback or async operation */
280 void silc_fsm_continue(void *fsm)
283 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
286 /* Continue after callback or async operation immediately */
288 void silc_fsm_continue_sync(void *fsm)
291 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
294 /* Return associated scheduler */
296 SilcSchedule silc_fsm_get_schedule(void *fsm)
302 /* Return thread's machine */
304 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
306 assert(thread->thread);
307 return (SilcFSM)thread->u.t.fsm;
312 void silc_fsm_set_context(void *fsm, void *fsm_context)
315 f->fsm_context = fsm_context;
320 void *silc_fsm_get_context(void *fsm)
323 return f->fsm_context;
326 /* Set state context */
328 void silc_fsm_set_state_context(void *fsm, void *state_context)
331 f->state_context = state_context;
334 /* Get state context */
336 void *silc_fsm_get_state_context(void *fsm)
339 return f->state_context;
342 /* Wait for thread to terminate */
344 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
347 #if defined(SILC_DEBUG)
349 #endif /* SILC_DEBUG */
350 t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
353 silc_fsm_sema_wait(t->u.t.sema, fsm);
359 SILC_TASK_CALLBACK(silc_fsm_run)
361 SilcFSM fsm = context;
362 SilcFSMStatus status;
364 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
368 status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
369 while (status == SILC_FSM_CONTINUE);
373 /* The machine is in hold */
374 SILC_LOG_DEBUG(("State wait %p", fsm));
375 fsm->synchronous = FALSE;
378 case SILC_FSM_FINISH:
379 /* Finish the state machine */
380 SILC_LOG_DEBUG(("State finish %p", fsm));
381 #if defined(SILC_DEBUG)
382 assert(!fsm->finished);
383 #endif /* SILC_DEBUG */
384 fsm->finished = TRUE;
386 /* If we are thread and using real threads, the FSM thread will finish
387 in the main thread, not in the created thread. */
388 if (fsm->thread && fsm->real_thread) {
389 silc_schedule_task_add_timeout(app_context, silc_fsm_finish, fsm, 0, 1);
390 silc_schedule_wakeup(app_context);
391 silc_schedule_stop(fsm->schedule);
395 /* Normal FSM operation */
396 if (fsm->synchronous)
397 silc_fsm_finish(fsm->schedule, app_context, 0, 0, fsm);
399 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish,
408 /* Finishes the FSM. This is always executed in the main thread, even
409 for FSM threads that were run in real threads. */
411 SILC_TASK_CALLBACK(silc_fsm_finish)
413 SilcFSM fsm = context;
415 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
417 fsm->next_state = NULL;
420 /* This is thread, send signal */
422 silc_fsm_thread_termination_post(fsm->u.t.sema);
423 silc_fsm_sema_free(fsm->u.t.sema);
424 fsm->u.t.sema = NULL;
427 /* Remove the thread from machine */
428 fsm->u.t.fsm->u.m.threads--;
430 /* Call the destructor callback only if the underlaying machine is
432 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
433 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
436 /* Call the destructor callback. */
438 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
442 /* Allocate FSM semaphore */
444 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
448 sema = silc_calloc(1, sizeof(*sema));
452 silc_fsm_sema_init(sema, fsm, value);
453 sema->allocated = TRUE;
458 /* Initializes FSM semaphore */
460 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
462 SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
463 #if defined(SILC_DEBUG)
464 assert(!fsm->thread);
465 #endif /* SILC_DEBUG */
466 memset(sema, 0, sizeof(*sema));
469 silc_list_init(sema->waiters, struct SilcFSMObject, next);
475 void silc_fsm_sema_free(SilcFSMSema sema)
477 if (sema->refcnt > 0)
479 #if defined(SILC_DEBUG)
480 assert(silc_list_count(sema->waiters) == 0);
481 #endif /* SILC_DEBUG */
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 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, 1);
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 termination 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, 1))
704 /* Run the scheduler */
705 silc_schedule(fsm->schedule);
708 silc_schedule_uninit(fsm->schedule);