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