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.
20 #include "silcincludes.h"
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);
30 SilcFSM silc_fsm_alloc(void *fsm_context,
31 SilcFSMDestructor destructor,
32 void *destructor_context,
33 SilcSchedule schedule)
37 fsm = silc_calloc(1, sizeof(*fsm));
41 if (!silc_fsm_init(fsm, fsm_context, destructor,
42 destructor_context, schedule)) {
52 SilcBool silc_fsm_init(SilcFSM fsm,
54 SilcFSMDestructor destructor,
55 void *destructor_context,
56 SilcSchedule schedule)
61 fsm->fsm_context = fsm_context;
62 fsm->destructor = destructor;
63 fsm->destructor_context = destructor_context;
64 fsm->schedule = schedule;
66 fsm->async_call = FALSE;
73 /* Allocate FSM thread. Internally machine and thread use same context. */
75 SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
77 SilcFSMThreadDestructor destructor,
78 void *destructor_context,
83 thread = silc_calloc(1, sizeof(*thread));
87 if (!silc_fsm_thread_init(thread, fsm, thread_context, destructor,
88 destructor_context, real_thread)) {
96 /* Initialize FSM thread. Internally machine and thread use same context. */
98 SilcBool 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->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->real_thread = real_thread;
119 thread->u.t.fsm = fsm;
124 /* Allocate lock for the machine if using real threads. */
125 if (real_thread && !fsm->u.m.lock)
126 if (!silc_mutex_alloc(&fsm->u.m.lock))
127 thread->real_thread = FALSE;
132 /* FSM is destroyed through scheduler to make sure that all dying
133 real system threads will have their finish callbacks scheduled before
134 this one (when SILC_FSM_THREAD_WAIT was used). */
136 SILC_TASK_CALLBACK(silc_fsm_free_final)
140 #if defined(SILC_DEBUG)
141 /* Machine must not have active threads */
142 if (!f->thread && f->u.m.threads)
143 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 /* FSM is uninitialized through scheduler to make sure that all dying
164 real system threads will have their finish callbacks scheduled before
165 this one (when SILC_FSM_THREAD_WAIT was used). */
167 SILC_TASK_CALLBACK(silc_fsm_uninit_final)
171 #if defined(SILC_DEBUG)
172 /* Machine must not have active threads */
173 if (!f->thread && f->u.m.threads)
174 assert(f->u.m.threads == 0);
175 #endif /* SILC_DEBUG */
177 if (!f->thread && f->u.m.lock)
178 silc_mutex_free(f->u.m.lock);
180 if (f->thread && f->u.t.sema)
181 silc_fsm_sema_free(f->u.t.sema);
184 /* Uninitializes FSM */
186 void silc_fsm_uninit(void *fsm)
189 silc_schedule_task_add_timeout(f->schedule, silc_fsm_uninit_final, f, 0, 1);
192 /* Task to start real thread. We start threads through scheduler, not
193 directly in silc_fsm_start. */
195 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
200 if (silc_thread_create(silc_fsm_thread, f, FALSE))
202 #endif /* SILC_THREADS */
204 SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
206 f->real_thread = FALSE;
208 silc_mutex_free(f->u.m.lock);
212 /* Normal FSM operation */
213 silc_fsm_continue_sync(f);
216 /* Start FSM in the specified state */
218 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
222 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
225 f->next_state = start_state;
226 f->synchronous = FALSE;
228 /* Start real threads through scheduler */
229 if (f->thread && f->real_thread) {
230 silc_schedule_task_add_timeout(f->schedule, silc_fsm_start_real_thread,
235 /* Normal FSM operation */
236 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
239 /* Start FSM in the specified state synchronously */
241 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
245 SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
248 f->next_state = start_state;
249 f->synchronous = TRUE;
251 /* Start real threads through scheduler */
252 if (f->thread && f->real_thread) {
253 silc_fsm_start_real_thread(f->schedule,
254 silc_schedule_get_context(f->schedule),
259 /* Normal FSM operation */
260 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
263 /* Set next FSM state */
265 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
268 f->next_state = next_state;
271 /* Continue after timeout */
273 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
274 SilcUInt32 seconds, SilcUInt32 useconds)
277 f->next_state = next_state;
278 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
282 /* Continue after callback or async operation */
284 void silc_fsm_continue(void *fsm)
287 silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
290 /* Continue after callback or async operation immediately */
292 void silc_fsm_continue_sync(void *fsm)
295 silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
298 /* Return associated scheduler */
300 SilcSchedule silc_fsm_get_schedule(void *fsm)
308 void *silc_fsm_get_context(void *fsm)
311 return f->fsm_context;
316 void silc_fsm_set_context(void *fsm, void *fsm_context)
319 f->fsm_context = fsm_context;
322 /* Wait for thread to terminate */
324 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
327 #if defined(SILC_DEBUG)
329 #endif /* SILC_DEBUG */
330 t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
333 silc_fsm_sema_wait(t->u.t.sema, fsm);
339 SILC_TASK_CALLBACK(silc_fsm_run)
341 SilcFSM fsm = context;
342 SilcFSMStatus status;
344 SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
347 status = fsm->next_state(fsm, fsm->fsm_context);
350 case SILC_FSM_CONTINUE:
351 /* Synchronously move to next state */
352 SILC_LOG_DEBUG(("State continue %p", fsm));
353 silc_fsm_run(schedule, app_context, type, fd, context);
357 /* The machine is in hold */
358 SILC_LOG_DEBUG(("State wait %p", fsm));
359 fsm->synchronous = FALSE;
362 case SILC_FSM_FINISH:
363 /* Finish the state machine */
364 SILC_LOG_DEBUG(("State finish %p", fsm));
365 #if defined(SILC_DEBUG)
366 assert(!fsm->finished);
367 #endif /* SILC_DEBUG */
368 fsm->finished = TRUE;
370 /* If we are thread and using real threads, the FSM thread will finish
371 in the main thread, not in the created thread. */
372 if (fsm->thread && fsm->real_thread) {
373 silc_schedule_task_add_timeout(app_context, silc_fsm_finish, fsm, 0, 1);
374 silc_schedule_wakeup(app_context);
375 silc_schedule_stop(fsm->schedule);
379 /* Normal FSM operation */
380 if (fsm->synchronous)
381 silc_fsm_finish(fsm->schedule, app_context, 0, 0, fsm);
383 silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish,
389 /* Finishes the FSM. This is always executed in the main thread, even
390 for FSM threads that were run in real threads. */
392 SILC_TASK_CALLBACK(silc_fsm_finish)
394 SilcFSM fsm = context;
396 SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
398 fsm->next_state = NULL;
401 /* This is thread, send signal */
403 silc_fsm_sema_post(fsm->u.t.sema);
404 silc_fsm_sema_wait(fsm->u.t.sema, fsm->u.t.sema->fsm);
405 silc_fsm_sema_free(fsm->u.t.sema);
406 fsm->u.t.sema = NULL;
409 /* Remove the thread from machine */
410 fsm->u.t.fsm->u.m.threads--;
412 /* Call the destructor callback only if the underlaying machine is
414 if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
415 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
418 /* Call the destructor callback. */
420 fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
424 /* Signalled, semaphore */
426 static void silc_fsm_signal(SilcFSM fsm)
428 SILC_LOG_DEBUG(("Signalled %s %p", fsm->thread ? "thread" : "FSM", fsm));
431 silc_fsm_continue(fsm);
433 /* Wakeup the destination's scheduler in case the signaller is a
435 silc_schedule_wakeup(fsm->schedule);
438 /* Allocate FSM semaphore */
440 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
444 sema = silc_calloc(1, sizeof(*sema));
448 silc_fsm_sema_init(sema, fsm, value);
453 /* Initializes FSM semaphore */
455 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
457 SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
458 #if defined(SILC_DEBUG)
459 assert(!fsm->thread);
460 #endif /* SILC_DEBUG */
462 silc_list_init(sema->waiters, struct SilcFSMObject, next);
468 void silc_fsm_sema_free(SilcFSMSema sema)
470 #if defined(SILC_DEBUG)
471 assert(silc_list_count(sema->waiters) == 0);
472 #endif /* SILC_DEBUG */
476 /* Wait until semaphore is non-zero. */
478 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
480 SilcMutex lock = sema->fsm->u.m.lock;
482 silc_mutex_lock(lock);
485 #if defined(SILC_DEBUG)
487 silc_list_start(sema->waiters);
488 while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
489 assert(entry != fsm);
490 #endif /* SILC_DEBUG */
492 SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
494 /* Add the FSM to waiter list */
495 silc_list_add(sema->waiters, fsm);
496 silc_mutex_unlock(lock);
500 SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
502 /* It is possible that this FSM is in the list so remove it */
503 silc_list_del(sema->waiters, fsm);
505 silc_mutex_unlock(lock);
509 /* Wait util semaphore is non-zero, or timeout occurs. */
511 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
512 SilcUInt32 seconds, SilcUInt32 useconds)
517 if (f->sema_timedout) {
518 SILC_LOG_DEBUG(("Semaphore was timedout"));
519 f->sema_timedout = FALSE;
523 value = silc_fsm_sema_wait(sema, fsm);
525 silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
526 f, seconds, useconds);
533 /* Semaphore timedout */
535 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
537 SilcFSM fsm = context;
538 SilcMutex lock = fsm->sema->fsm->u.m.lock;
540 SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
542 /* Remove the waiter from the semaphore */
543 silc_mutex_lock(lock);
544 silc_list_del(fsm->sema->waiters, fsm);
545 silc_mutex_unlock(lock);
548 fsm->sema_timedout = TRUE;
551 silc_fsm_continue(fsm);
554 /* Increase semaphore */
556 void silc_fsm_sema_post(SilcFSMSema sema)
559 SilcMutex lock = sema->fsm->u.m.lock;
561 SILC_LOG_DEBUG(("Posting semaphore %p", sema));
563 silc_mutex_lock(lock);
566 silc_list_start(sema->waiters);
567 while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
569 silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
573 silc_fsm_signal(fsm);
576 silc_mutex_unlock(lock);
581 static void *silc_fsm_thread(void *context)
583 SilcFSM fsm = context;
584 SilcSchedule old = fsm->schedule;
586 SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
588 /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
589 cannot be used in this thread. Application may still use it if it
590 wants but we use our own. */
591 fsm->schedule = silc_schedule_init(0, old);
595 /* Start the FSM thread */
596 if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 1))
599 /* Run the scheduler */
600 silc_schedule(fsm->schedule);
603 silc_schedule_uninit(fsm->schedule);