SILC_FSM_* state macros doesn't return anymore. They must be
[crypto.git] / lib / silcutil / silcfsm.c
1 /*
2
3   silcfsm.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 - 2006 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   /* It is possible that this FSM is in the list so remove it */
564   silc_list_del(event->waiters, fsm);
565   event->value--;
566   silc_mutex_unlock(lock);
567   return 1;
568 }
569
570 /* Wait util event is non-zero, or timeout occurs. */
571
572 SilcUInt32 silc_fsm_event_timedwait(SilcFSMEvent event, void *fsm,
573                                     SilcUInt32 seconds, SilcUInt32 useconds,
574                                     SilcBool *ret_to)
575 {
576   SilcMutex lock = event->fsm->u.m.lock;
577   SilcFSM f = fsm;
578   SilcUInt32 value;
579
580   silc_mutex_lock(lock);
581
582   if (f->event_timedout) {
583     SILC_LOG_DEBUG(("Event waiting timedout"));
584     f->event_timedout = FALSE;
585     if (ret_to)
586       *ret_to = TRUE;
587     silc_mutex_unlock(lock);
588     return 1;
589   }
590
591   silc_mutex_unlock(lock);
592
593   value = silc_fsm_event_wait(event, fsm);
594   if (!value) {
595     silc_schedule_task_add_timeout(f->schedule, silc_fsm_event_timedout,
596                                    f, seconds, useconds);
597     f->event = event;
598   }
599
600   if (ret_to)
601     *ret_to = FALSE;
602
603   return value;
604 }
605
606 /* Event timedout */
607
608 SILC_TASK_CALLBACK(silc_fsm_event_timedout)
609 {
610   SilcFSM fsm = context;
611   SilcMutex lock = fsm->event->fsm->u.m.lock;
612
613   SILC_LOG_DEBUG(("Event %p timedout", fsm->event));
614
615   /* Remove the waiter from the event waiters list */
616   silc_mutex_lock(lock);
617   silc_list_del(fsm->event->waiters, fsm);
618
619   /* Continue */
620   if (fsm->event) {
621     silc_fsm_continue(fsm);
622     fsm->event_timedout = TRUE;
623     fsm->event = NULL;
624   }
625
626   silc_mutex_unlock(lock);
627 }
628
629 /* Signalled, event */
630
631 SILC_TASK_CALLBACK(silc_fsm_signal)
632 {
633   SilcFSMEventSignal p = context;
634   SilcMutex lock = p->event->fsm->u.m.lock;
635
636   /* If the event value has went to zero while we've been waiting this
637      callback, the event has been been signalled already.  It can happen
638      when using real threads because the FSM may not be in waiting state
639      when the sempahore is posted. */
640   silc_mutex_lock(lock);
641   if (!p->event->value) {
642     silc_mutex_unlock(lock);
643     silc_fsm_event_unref(p->event);
644     silc_free(p);
645     return;
646   }
647   silc_mutex_unlock(lock);
648
649   SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
650                   p->fsm));
651
652   /* Signal */
653   silc_fsm_continue_sync(p->fsm);
654
655   silc_fsm_event_unref(p->event);
656   silc_free(p);
657 }
658
659 /* Signal event */
660
661 void silc_fsm_event_signal(SilcFSMEvent event)
662 {
663   SilcFSM fsm;
664   SilcFSMEventSignal p;
665   SilcMutex lock = event->fsm->u.m.lock;
666
667   SILC_LOG_DEBUG(("Signal event %p", event));
668
669   silc_mutex_lock(lock);
670
671   event->value++;
672   silc_list_start(event->waiters);
673   while ((fsm = silc_list_get(event->waiters)) != SILC_LIST_END) {
674     if (fsm->event) {
675       silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_event_timedout,
676                                     fsm);
677       fsm->event = NULL;
678     }
679
680     p = silc_calloc(1, sizeof(*p));
681     if (silc_unlikely(!p))
682       continue;
683     p->event = event;
684     p->fsm = fsm;
685     silc_fsm_event_ref(event);
686
687     /* Signal through scheduler.  Wake up destination scheduler in case
688        caller is a real thread. */
689     silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
690     silc_schedule_wakeup(fsm->schedule);
691   }
692
693   silc_mutex_unlock(lock);
694 }
695
696 /* Post thread termination event.  Special function used only to
697    signal thread termination when SILC_FSM_THREAD_WAIT was used. */
698
699 static void silc_fsm_thread_termination_signal(SilcFSMEvent event)
700 {
701   SilcFSM fsm;
702   SilcMutex lock = event->fsm->u.m.lock;
703
704   SILC_LOG_DEBUG(("Post thread terminate event %p", event));
705
706   silc_mutex_lock(lock);
707
708   silc_list_start(event->waiters);
709   while ((fsm = silc_list_get(event->waiters)) != SILC_LIST_END) {
710     /* Signal on thread termination.  Wake up destination scheduler in case
711        caller is a real thread. */
712     silc_list_del(event->waiters, fsm);
713     silc_fsm_continue(fsm);
714     silc_schedule_wakeup(fsm->schedule);
715   }
716
717   silc_mutex_unlock(lock);
718 }
719
720 /* Real thread */
721
722 void *silc_fsm_thread(void *context)
723 {
724   SilcFSM fsm = context;
725   SilcSchedule old = fsm->schedule;
726
727   SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
728
729   /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
730      cannot be used in this thread.  Application may still use it if it
731      wants but we use our own. */
732   fsm->schedule = silc_schedule_init(0, old);
733   if (silc_unlikely(!fsm->schedule))
734     return NULL;
735
736   /* Start the FSM thread */
737   if (silc_unlikely(!silc_schedule_task_add_timeout(fsm->schedule,
738                                                     silc_fsm_run, fsm, 0, 0)))
739     return NULL;
740
741   /* Run the scheduler */
742   silc_schedule(fsm->schedule);
743
744   /* Free resources */
745   silc_schedule_uninit(fsm->schedule);
746
747   fsm->schedule = old;
748
749   /* Finish the FSM thread in the main thread */
750   SILC_ASSERT(fsm->finished);
751   silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish_fsm,
752                                  fsm, 0, 0);
753   silc_schedule_wakeup(fsm->schedule);
754
755   return NULL;
756 }