1 /*
2 
3   silcfsm.c
4 
5   Author: Pekka Riikonen <priikone@silcnet.org>
6 
7   Copyright (C) 2005 - 2007 Pekka Riikonen
8 
9   The contents of this file are subject to one of the Licenses specified
10   in the COPYING file;  You may not use this file except in compliance
11   with the License.
12 
13   The software distributed under the License is distributed on an "AS IS"
14   basis, in the hope that it will be useful, but WITHOUT WARRANTY OF ANY
15   KIND, either expressed or implied.  See the COPYING file for more
16   information.
17 
18 */
19 
20 #include "silc.h"
21 
22 SILC_TASK_CALLBACK(silc_fsm_run);
23 SILC_TASK_CALLBACK(silc_fsm_finish_fsm);
24 SILC_TASK_CALLBACK(silc_fsm_event_timedout);
25 SILC_TASK_CALLBACK(silc_fsm_start_real_thread);
26 static void silc_fsm_thread_termination_signal(SilcFSMEvent event);
27 static void silc_fsm_event_ref(SilcFSMEvent event);
28 static void silc_fsm_event_unref(SilcFSMEvent event);
29 void *silc_fsm_thread(void *context);
30 
31 /* Allocate FSM */
32 
silc_fsm_alloc(void * fsm_context,SilcFSMDestructor destructor,void * destructor_context,SilcSchedule schedule)33 SilcFSM silc_fsm_alloc(void *fsm_context,
34                        SilcFSMDestructor destructor,
35                        void *destructor_context,
36                        SilcSchedule schedule)
37 {
38   SilcFSM fsm;
39 
40   fsm = silc_calloc(1, sizeof(*fsm));
41   if (silc_unlikely(!fsm))
42     return NULL;
43 
44   if (silc_unlikely(!silc_fsm_init(fsm, fsm_context, destructor,
45 				   destructor_context, schedule))) {
46     silc_free(fsm);
47     return NULL;
48   }
49 
50   return fsm;
51 }
52 
53 /* Initialize FSM */
54 
silc_fsm_init(SilcFSM fsm,void * fsm_context,SilcFSMDestructor destructor,void * destructor_context,SilcSchedule schedule)55 SilcBool silc_fsm_init(SilcFSM fsm,
56 		       void *fsm_context,
57 		       SilcFSMDestructor destructor,
58 		       void *destructor_context,
59 		       SilcSchedule schedule)
60 {
61   if (!schedule)
62     return FALSE;
63 
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;
69   fsm->thread = FALSE;
70   fsm->async_call = FALSE;
71   fsm->started = FALSE;
72   fsm->u.m.lock = NULL;
73   silc_atomic_init32(&fsm->u.m.threads, 0);
74 
75   return TRUE;
76 }
77 
78 /* Allocate FSM thread.  Internally machine and thread use same context. */
79 
silc_fsm_thread_alloc(SilcFSM fsm,void * thread_context,SilcFSMThreadDestructor destructor,void * destructor_context,SilcBool real_thread)80 SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
81 				    void *thread_context,
82 				    SilcFSMThreadDestructor destructor,
83 				    void *destructor_context,
84 				    SilcBool real_thread)
85 {
86   SilcFSMThread thread;
87 
88   thread = silc_calloc(1, sizeof(*thread));
89   if (silc_unlikely(!thread))
90     return NULL;
91 
92   silc_fsm_thread_init(thread, fsm, thread_context, destructor,
93 		       destructor_context, real_thread);
94   return thread;
95 }
96 
97 /* Initialize FSM thread.  Internally machine and thread use same context. */
98 
silc_fsm_thread_init(SilcFSMThread thread,SilcFSM fsm,void * thread_context,SilcFSMThreadDestructor destructor,void * destructor_context,SilcBool real_thread)99 void silc_fsm_thread_init(SilcFSMThread thread,
100 			  SilcFSM fsm,
101 			  void *thread_context,
102 			  SilcFSMThreadDestructor destructor,
103 			  void *destructor_context,
104 			  SilcBool real_thread)
105 {
106   SILC_LOG_DEBUG(("Initializing new thread %p (%s)",
107 		  thread, real_thread ? "real" : "FSM"));
108 
109   SILC_ASSERT(!fsm->thread);
110 
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;
121 
122   /* Add to machine */
123   silc_atomic_add_int32(&fsm->u.m.threads, 1);
124 
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;
129 }
130 
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). */
134 
SILC_TASK_CALLBACK(silc_fsm_free_final)135 SILC_TASK_CALLBACK(silc_fsm_free_final)
136 {
137   SilcFSM f = context;
138 
139 #if defined(SILC_DEBUG)
140   /* We must be finished */
141   SILC_ASSERT(f->finished);
142 
143   /* Machine must not have active threads */
144   if (!f->thread && silc_atomic_get_int32(&f->u.m.threads))
145     SILC_ASSERT(silc_atomic_get_int32(&f->u.m.threads) == 0);
146 #endif /* SILC_DEBUG */
147 
148   if (!f->thread && f->u.m.lock)
149     silc_mutex_free(f->u.m.lock);
150 
151   if (f->thread && f->u.t.event)
152     silc_fsm_event_free(f->u.t.event);
153 
154   if (!f->thread)
155     silc_atomic_uninit32(&f->u.m.threads);
156 
157   silc_free(f);
158 }
159 
160 /* Free FSM */
161 
silc_fsm_free(void * fsm)162 void silc_fsm_free(void *fsm)
163 {
164   SilcFSM f = fsm;
165   if (!f->thread)
166     if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final,
167 				       f, 0, 0))
168       return;
169   silc_fsm_free_final(f->schedule, silc_schedule_get_context(f->schedule),
170 		      0, 0, f);
171 }
172 
173 /* Task to start real thread. We start threads through scheduler, not
174    directly in silc_fsm_start. */
175 
SILC_TASK_CALLBACK(silc_fsm_start_real_thread)176 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
177 {
178   SilcFSM f = context;
179 
180 #ifdef SILC_THREADS
181   if (silc_thread_create(silc_fsm_thread, f, FALSE))
182     return;
183 #endif /* SILC_THREADS */
184 
185   SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
186 
187   /* Normal FSM operation */
188   f->real_thread = FALSE;
189   silc_fsm_continue_sync(f);
190 }
191 
192 /* Start FSM in the specified state */
193 
silc_fsm_start(void * fsm,SilcFSMStateCallback start_state)194 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
195 {
196   SilcFSM f = fsm;
197 
198   SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
199 
200   f->finished = FALSE;
201   f->next_state = start_state;
202   f->synchronous = FALSE;
203   f->started = TRUE;
204 
205   /* Start real thread through scheduler */
206   if (f->thread && f->real_thread) {
207     if (!silc_schedule_task_add_timeout(f->schedule,
208 					silc_fsm_start_real_thread,
209 					f, 0, 0))
210       silc_fsm_start_real_thread(f->schedule,
211 				 silc_schedule_get_context(f->schedule),
212 				 0, 0, f);
213     silc_schedule_wakeup(f->schedule);
214     return;
215   }
216 
217   /* Normal FSM operation */
218   if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
219     silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
220 
221   /* Wakeup scheduler in case we are starting this thread from another
222      real thread. */
223   if (f->thread)
224     silc_schedule_wakeup(f->schedule);
225 }
226 
227 /* Start FSM in the specified state synchronously */
228 
silc_fsm_start_sync(void * fsm,SilcFSMStateCallback start_state)229 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
230 {
231   SilcFSM f = fsm;
232 
233   SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
234 
235   f->finished = FALSE;
236   f->next_state = start_state;
237   f->synchronous = TRUE;
238   f->started = TRUE;
239 
240   /* Start real thread directly */
241   if (f->thread && f->real_thread) {
242     silc_fsm_start_real_thread(f->schedule,
243 			       silc_schedule_get_context(f->schedule),
244 			       0, 0, f);
245     return;
246   }
247 
248   /* Normal FSM operation */
249   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
250 }
251 
252 /* Set next FSM state */
253 
silc_fsm_next(void * fsm,SilcFSMStateCallback next_state)254 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
255 {
256   SilcFSM f = fsm;
257   f->next_state = next_state;
258 }
259 
260 /* Continue after timeout */
261 
silc_fsm_next_later(void * fsm,SilcFSMStateCallback next_state,SilcUInt32 seconds,SilcUInt32 useconds)262 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
263 			 SilcUInt32 seconds, SilcUInt32 useconds)
264 {
265   SilcFSM f = fsm;
266 
267   f->next_state = next_state;
268   if (!seconds && !useconds)
269     return;
270 
271   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
272 				 seconds, useconds);
273   f->next_later = TRUE;
274 
275   /* Wakeup up the scheduler just in case this was called from another
276      thread. */
277   silc_schedule_wakeup(f->schedule);
278 }
279 
280 /* Continue after callback or async operation */
281 
silc_fsm_continue(void * fsm)282 void silc_fsm_continue(void *fsm)
283 {
284   SilcFSM f = fsm;
285 
286   if (f->next_later) {
287     /* Cancel next_later timeout */
288     silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
289     f->next_later = FALSE;
290   }
291 
292   if (!silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 0))
293     silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
294 
295   /* Wakeup up the scheduler just in case this was called from another
296      thread. */
297   silc_schedule_wakeup(f->schedule);
298 }
299 
300 /* Continue after callback or async operation immediately */
301 
silc_fsm_continue_sync(void * fsm)302 void silc_fsm_continue_sync(void *fsm)
303 {
304   SilcFSM f = fsm;
305   if (f->next_later) {
306     silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
307     f->next_later = FALSE;
308   }
309   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
310 }
311 
312 /* Finish FSM */
313 
silc_fsm_finish(void * fsm)314 void silc_fsm_finish(void *fsm)
315 {
316   SilcFSM f = fsm;
317 
318   SILC_ASSERT(!f->finished);
319 
320   f->started = FALSE;
321   f->finished = TRUE;
322 
323   silc_schedule_task_del_by_all(f->schedule, 0, silc_fsm_run, f);
324   f->next_later = FALSE;
325 
326   /* If we are thread and using real threads, the FSM thread will finish
327      after the real thread has finished, in the main thread. */
328   if (f->thread && f->real_thread) {
329     /* Stop the real thread's scheduler to finish the thread */
330     silc_schedule_stop(f->schedule);
331     silc_schedule_wakeup(f->schedule);
332     return;
333   }
334 
335   /* Normal FSM operation */
336   if (!f->synchronous)
337     if (silc_schedule_task_add_timeout(f->schedule, silc_fsm_finish_fsm,
338 				       f, 0, 0))
339       return;
340 
341   silc_fsm_finish_fsm(f->schedule, silc_schedule_get_context(f->schedule),
342 		      0, 0, fsm);
343 }
344 
345 /* Return associated scheduler */
346 
silc_fsm_get_schedule(void * fsm)347 SilcSchedule silc_fsm_get_schedule(void *fsm)
348 {
349   SilcFSM f = fsm;
350   return f->schedule;
351 }
352 
353 /* Return thread's machine */
354 
silc_fsm_get_machine(SilcFSMThread thread)355 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
356 {
357   SILC_ASSERT(thread->thread);
358   return (SilcFSM)thread->u.t.fsm;
359 }
360 
361 /* Returns TRUE if FSM is started */
362 
silc_fsm_is_started(void * fsm)363 SilcBool silc_fsm_is_started(void *fsm)
364 {
365   SilcFSM f = fsm;
366   return f->started;
367 }
368 
369 /* Set context */
370 
silc_fsm_set_context(void * fsm,void * fsm_context)371 void silc_fsm_set_context(void *fsm, void *fsm_context)
372 {
373   SilcFSM f = fsm;
374   f->fsm_context = fsm_context;
375 }
376 
377 /* Get context */
378 
silc_fsm_get_context(void * fsm)379 void *silc_fsm_get_context(void *fsm)
380 {
381   SilcFSM f = fsm;
382   return f->fsm_context;
383 }
384 
385 /* Set state context */
386 
silc_fsm_set_state_context(void * fsm,void * state_context)387 void silc_fsm_set_state_context(void *fsm, void *state_context)
388 {
389   SilcFSM f = fsm;
390   f->state_context = state_context;
391 }
392 
393 /* Get state context */
394 
silc_fsm_get_state_context(void * fsm)395 void *silc_fsm_get_state_context(void *fsm)
396 {
397   SilcFSM f = fsm;
398   return f->state_context;
399 }
400 
401 /* Wait for thread to terminate */
402 
silc_fsm_thread_wait(void * fsm,void * thread)403 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
404 {
405   SilcFSM t = thread;
406 
407   SILC_ASSERT(t->thread);
408 
409   t->u.t.event = silc_fsm_event_alloc(t->u.t.fsm);
410   if (!t->u.t.event)
411     return FALSE;
412 
413   SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
414   silc_fsm_event_wait(t->u.t.event, fsm);
415   return TRUE;
416 }
417 
418 /* The machine */
419 
SILC_TASK_CALLBACK(silc_fsm_run)420 SILC_TASK_CALLBACK(silc_fsm_run)
421 {
422   SilcFSM fsm = context;
423   SilcFSMStatus status;
424 
425   SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
426 
427   /* Run the states */
428   do
429     status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
430   while (status == SILC_FSM_ST_CONTINUE);
431 
432   switch (status) {
433   case SILC_FSM_ST_YIELD:
434     /* Continue through scheduler */
435     silc_fsm_continue(fsm);
436     break;
437 
438   case SILC_FSM_ST_WAIT:
439     /* The machine is in hold */
440     SILC_LOG_DEBUG(("State wait %p", fsm));
441     fsm->synchronous = FALSE;
442     break;
443 
444   case SILC_FSM_ST_FINISH:
445     /* Finish the state machine */
446     SILC_LOG_DEBUG(("State finish %p", fsm));
447     silc_fsm_finish(fsm);
448     break;
449 
450   default:
451     break;
452   }
453 }
454 
455 /* Finishes the FSM.  This is always executed in the main thread, even
456    for FSM threads that were run in real threads. */
457 
SILC_TASK_CALLBACK(silc_fsm_finish_fsm)458 SILC_TASK_CALLBACK(silc_fsm_finish_fsm)
459 {
460   SilcFSM fsm = context;
461 
462   SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
463 
464   fsm->next_state = NULL;
465 
466   if (fsm->thread) {
467     /* This is thread, send signal */
468     if (fsm->u.t.event) {
469       silc_fsm_thread_termination_signal(fsm->u.t.event);
470       silc_fsm_event_free(fsm->u.t.event);
471       fsm->u.t.event = NULL;
472     }
473 
474     /* Remove the thread from machine */
475     silc_atomic_sub_int32(&fsm->u.t.fsm->u.m.threads, 1);
476 
477     /* Call the destructor callback only if the underlaying machine is
478        still valid. */
479     if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
480       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
481 
482   } else {
483     /* Machine must not have active threads */
484     assert(silc_atomic_get_int32(&fsm->u.m.threads) == 0);
485 
486     if (fsm->u.m.lock) {
487       silc_mutex_free(fsm->u.m.lock);
488       fsm->u.m.lock = NULL;
489     }
490 
491     /* Call the destructor callback. */
492     if (fsm->destructor)
493       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
494   }
495 }
496 
497 /* Allocate FSM event */
498 
silc_fsm_event_alloc(SilcFSM fsm)499 SilcFSMEvent silc_fsm_event_alloc(SilcFSM fsm)
500 {
501   SilcFSMEvent event;
502 
503   event = silc_calloc(1, sizeof(*event));
504   if (silc_unlikely(!event))
505     return NULL;
506 
507   silc_fsm_event_init(event, fsm);
508   event->allocated = TRUE;
509 
510   return event;
511 }
512 
513 /* Initializes FSM event */
514 
silc_fsm_event_init(SilcFSMEvent event,SilcFSM fsm)515 void silc_fsm_event_init(SilcFSMEvent event, SilcFSM fsm)
516 {
517   SILC_LOG_DEBUG(("Initializing event %p", event));
518   SILC_ASSERT(!fsm->thread);
519   memset(event, 0, sizeof(*event));
520   event->fsm = fsm;
521   event->refcnt = 0;
522   silc_list_init(event->waiters, struct SilcFSMObject, next);
523 }
524 
525 /* Free event */
526 
silc_fsm_event_free(SilcFSMEvent event)527 void silc_fsm_event_free(SilcFSMEvent event)
528 {
529   if (event->refcnt > 0)
530     return;
531   if (silc_list_count(event->waiters) > 0)
532     return;
533   silc_free(event);
534 }
535 
536 /* Reference event */
537 
silc_fsm_event_ref(SilcFSMEvent event)538 static void silc_fsm_event_ref(SilcFSMEvent event)
539 {
540   event->refcnt++;
541 }
542 
543 /* Unreference event */
544 
silc_fsm_event_unref(SilcFSMEvent event)545 static void silc_fsm_event_unref(SilcFSMEvent event)
546 {
547   event->refcnt--;
548   if (event->refcnt == 0 && event->allocated)
549     silc_fsm_event_free(event);
550 }
551 
552 /* Wait until event is non-zero. */
553 
silc_fsm_event_wait(SilcFSMEvent event,void * fsm)554 SilcUInt32 silc_fsm_event_wait(SilcFSMEvent event, void *fsm)
555 {
556   SilcMutex lock = event->fsm->u.m.lock;
557 
558   silc_mutex_lock(lock);
559 
560   if (!event->value) {
561 #if defined(SILC_DEBUG)
562     SilcFSM entry;
563     silc_list_start(event->waiters);
564     while ((entry = silc_list_get(event->waiters)))
565       SILC_ASSERT(entry != fsm);
566 #endif /* SILC_DEBUG */
567 
568     SILC_LOG_DEBUG(("Waiting for event %p", event));
569 
570     /* Add the FSM to waiter list */
571     silc_list_add(event->waiters, fsm);
572     silc_mutex_unlock(lock);
573     return 0;
574   }
575 
576   SILC_LOG_DEBUG(("Received event %p", event));
577 
578   /* Remove from waiting */
579   silc_list_del(event->waiters, fsm);
580 
581   /* Decrease the counter only after all waiters have acquired the signal. */
582   if (!silc_list_count(event->waiters))
583     event->value--;
584 
585   silc_mutex_unlock(lock);
586   return 1;
587 }
588 
589 /* Wait util event is non-zero, or timeout occurs. */
590 
silc_fsm_event_timedwait(SilcFSMEvent event,void * fsm,SilcUInt32 seconds,SilcUInt32 useconds,SilcBool * ret_to)591 SilcUInt32 silc_fsm_event_timedwait(SilcFSMEvent event, void *fsm,
592 				    SilcUInt32 seconds, SilcUInt32 useconds,
593 				    SilcBool *ret_to)
594 {
595   SilcMutex lock = event->fsm->u.m.lock;
596   SilcFSM f = fsm;
597   SilcUInt32 value;
598 
599   silc_mutex_lock(lock);
600 
601   if (f->event_timedout) {
602     SILC_LOG_DEBUG(("Event waiting timedout"));
603     f->event_timedout = FALSE;
604     if (ret_to)
605       *ret_to = TRUE;
606     silc_mutex_unlock(lock);
607     return 1;
608   }
609 
610   silc_mutex_unlock(lock);
611 
612   value = silc_fsm_event_wait(event, fsm);
613   if (!value) {
614     silc_schedule_task_add_timeout(f->schedule, silc_fsm_event_timedout,
615 				   f, seconds, useconds);
616     f->event = event;
617   }
618 
619   if (ret_to)
620     *ret_to = FALSE;
621 
622   return value;
623 }
624 
625 /* Event timedout */
626 
SILC_TASK_CALLBACK(silc_fsm_event_timedout)627 SILC_TASK_CALLBACK(silc_fsm_event_timedout)
628 {
629   SilcFSM fsm = context;
630   SilcMutex lock = fsm->event->fsm->u.m.lock;
631 
632   SILC_LOG_DEBUG(("Event %p timedout", fsm->event));
633 
634   /* Remove the waiter from the event waiters list */
635   silc_mutex_lock(lock);
636   silc_list_del(fsm->event->waiters, fsm);
637 
638   /* Continue */
639   if (fsm->event) {
640     silc_fsm_continue(fsm);
641     fsm->event_timedout = TRUE;
642     fsm->event = NULL;
643   }
644 
645   silc_mutex_unlock(lock);
646 }
647 
648 /* Signalled, event */
649 
SILC_TASK_CALLBACK(silc_fsm_signal)650 SILC_TASK_CALLBACK(silc_fsm_signal)
651 {
652   SilcFSMEventSignal p = context;
653   SilcMutex lock = p->event->fsm->u.m.lock;
654   SilcFSM fsm;
655 
656   /* We have to check for couple of things before delivering the signal. */
657 
658   /* If the event value has went to zero while we've been waiting this
659      callback, the event has been been signalled already.  It can happen
660      when using real threads because the FSM may not be in waiting state
661      when the event is signalled. */
662   silc_mutex_lock(lock);
663   if (!p->event->value) {
664     silc_mutex_unlock(lock);
665     silc_fsm_event_unref(p->event);
666     silc_free(p);
667     return;
668   }
669 
670   /* If the waiter is not waiting anymore, don't deliver the signal.  It
671      can happen if there were multiple signallers and the waiter went away
672      after the first signal. */
673   silc_list_start(p->event->waiters);
674   while ((fsm = silc_list_get(p->event->waiters)))
675     if (fsm == p->fsm)
676       break;
677   if (!fsm) {
678     silc_mutex_unlock(lock);
679     silc_fsm_event_unref(p->event);
680     silc_free(p);
681     return;
682   }
683   silc_mutex_unlock(lock);
684 
685   SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
686 		  p->fsm));
687 
688   /* Signal */
689   silc_fsm_continue_sync(p->fsm);
690 
691   silc_fsm_event_unref(p->event);
692   silc_free(p);
693 }
694 
695 /* Signal event */
696 
silc_fsm_event_signal(SilcFSMEvent event)697 void silc_fsm_event_signal(SilcFSMEvent event)
698 {
699   SilcFSM fsm;
700   SilcFSMEventSignal p;
701   SilcMutex lock = event->fsm->u.m.lock;
702 
703   SILC_LOG_DEBUG(("Signal event %p", event));
704 
705   silc_mutex_lock(lock);
706 
707   event->value++;
708   silc_list_start(event->waiters);
709   while ((fsm = silc_list_get(event->waiters))) {
710     if (fsm->event) {
711       silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
712 				    fsm);
713       fsm->event = NULL;
714     }
715 
716     p = silc_calloc(1, sizeof(*p));
717     if (silc_unlikely(!p))
718       continue;
719     p->event = event;
720     p->fsm = fsm;
721     silc_fsm_event_ref(event);
722 
723     /* Signal through scheduler.  Wake up destination scheduler in case
724        caller is a real thread. */
725     silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
726     silc_schedule_wakeup(fsm->schedule);
727   }
728 
729   silc_mutex_unlock(lock);
730 }
731 
732 /* Post thread termination event.  Special function used only to
733    signal thread termination when SILC_FSM_THREAD_WAIT was used. */
734 
silc_fsm_thread_termination_signal(SilcFSMEvent event)735 static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
736 {
737   SilcFSM fsm;
738   SilcMutex lock = event->fsm->u.m.lock;
739 
740   SILC_LOG_DEBUG(("Post thread terminate event %p", event));
741 
742   silc_mutex_lock(lock);
743 
744   silc_list_start(event->waiters);
745   while ((fsm = silc_list_get(event->waiters))) {
746     /* Signal on thread termination.  Wake up destination scheduler in case
747        caller is a real thread. */
748     silc_list_del(event->waiters, fsm);
749     silc_fsm_continue(fsm);
750     silc_schedule_wakeup(fsm->schedule);
751   }
752 
753   silc_mutex_unlock(lock);
754 }
755 
756 /* Real thread */
757 
silc_fsm_thread(void * context)758 void *silc_fsm_thread(void *context)
759 {
760   SilcFSM fsm = context;
761   SilcSchedule old = fsm->schedule;
762 
763   SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
764 
765   /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
766      cannot be used in this thread.  Application may still use it if it
767      wants but we use our own. */
768   fsm->schedule = silc_schedule_init(0, old);
769   if (silc_unlikely(!fsm->schedule))
770     return NULL;
771 
772   /* Start the FSM thread */
773   if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
774 						    silc_fsm_run, fsm, 0, 0)))
775     return NULL;
776 
777   /* Run the scheduler */
778   silc_schedule(fsm->schedule);
779 
780   /* Free resources */
781   silc_schedule_uninit(fsm->schedule);
782 
783   fsm->schedule = old;
784 
785   /* Finish the FSM thread in the main thread */
786   SILC_ASSERT(fsm->finished);
787   silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
788 				 fsm, 0, 0);
789   silc_schedule_wakeup(fsm->schedule);
790 
791   return NULL;
792 }
793