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