Handle failed timeout additions (in case scheduler is stopped).
[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_sema_timedout);
25 SILC_TASK_CALLBACK(silc_fsm_start_real_thread);
26 static void *silc_fsm_thread(void *context);
27 static void silc_fsm_thread_termination_post(SilcFSMSema sema);
28 static void silc_fsm_sema_ref(SilcFSMSema sema);
29 static void silc_fsm_sema_unref(SilcFSMSema sema);
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 (!fsm)
42     return NULL;
43
44   if (!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 (!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.sema)
152     silc_fsm_sema_free(f->u.t.sema);
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.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
392   if (!t->u.t.sema)
393     return FALSE;
394
395   SILC_LOG_DEBUG(("Waiting for thread %p to terminate", thread));
396   silc_fsm_sema_wait(t->u.t.sema, 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_YIELD:
416     /* Continue through scheduler */
417     silc_fsm_continue(fsm);
418     break;
419
420   case SILC_FSM_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_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.sema) {
451       silc_fsm_thread_termination_post(fsm->u.t.sema);
452       silc_fsm_sema_free(fsm->u.t.sema);
453       fsm->u.t.sema = 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 semaphore */
477
478 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
479 {
480   SilcFSMSema sema;
481
482   sema = silc_calloc(1, sizeof(*sema));
483   if (!sema)
484     return NULL;
485
486   silc_fsm_sema_init(sema, fsm, value);
487   sema->allocated = TRUE;
488
489   return sema;
490 }
491
492 /* Initializes FSM semaphore */
493
494 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
495 {
496   SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
497   SILC_ASSERT(!fsm->thread);
498   memset(sema, 0, sizeof(*sema));
499   sema->fsm = fsm;
500   sema->refcnt = 0;
501   silc_list_init(sema->waiters, struct SilcFSMObject, next);
502   sema->value = value;
503 }
504
505 /* Free semaphore */
506
507 void silc_fsm_sema_free(SilcFSMSema sema)
508 {
509   if (sema->refcnt > 0)
510     return;
511   if (silc_list_count(sema->waiters) > 0)
512     return;
513   silc_free(sema);
514 }
515
516 /* Reference semaphore */
517
518 static void silc_fsm_sema_ref(SilcFSMSema sema)
519 {
520   sema->refcnt++;
521 }
522
523 /* Unreference semaphore */
524
525 static void silc_fsm_sema_unref(SilcFSMSema sema)
526 {
527   sema->refcnt--;
528   if (sema->refcnt == 0 && sema->allocated)
529     silc_fsm_sema_free(sema);
530 }
531
532 /* Wait until semaphore is non-zero. */
533
534 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
535 {
536   SilcMutex lock = sema->fsm->u.m.lock;
537
538   silc_mutex_lock(lock);
539
540   if (!sema->value) {
541 #if defined(SILC_DEBUG)
542     SilcFSM entry;
543     silc_list_start(sema->waiters);
544     while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
545       SILC_ASSERT(entry != fsm);
546 #endif /* SILC_DEBUG */
547
548     SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
549
550     /* Add the FSM to waiter list */
551     silc_list_add(sema->waiters, fsm);
552     silc_mutex_unlock(lock);
553     return 0;
554   }
555
556   SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
557
558   /* It is possible that this FSM is in the list so remove it */
559   silc_list_del(sema->waiters, fsm);
560   sema->value--;
561   silc_mutex_unlock(lock);
562   return 1;
563 }
564
565 /* Wait util semaphore is non-zero, or timeout occurs. */
566
567 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
568                                    SilcUInt32 seconds, SilcUInt32 useconds,
569                                    SilcBool *ret_to)
570 {
571   SilcMutex lock = sema->fsm->u.m.lock;
572   SilcFSM f = fsm;
573   SilcUInt32 value;
574
575   silc_mutex_lock(lock);
576
577   if (f->sema_timedout) {
578     SILC_LOG_DEBUG(("Semaphore was timedout"));
579     f->sema_timedout = FALSE;
580     if (ret_to)
581       *ret_to = TRUE;
582     silc_mutex_unlock(lock);
583     return 1;
584   }
585
586   silc_mutex_unlock(lock);
587
588   value = silc_fsm_sema_wait(sema, fsm);
589   if (!value) {
590     silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
591                                    f, seconds, useconds);
592     f->sema = sema;
593   }
594
595   if (ret_to)
596     *ret_to = FALSE;
597
598   return value;
599 }
600
601 /* Semaphore timedout */
602
603 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
604 {
605   SilcFSM fsm = context;
606   SilcMutex lock = fsm->sema->fsm->u.m.lock;
607
608   SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
609
610   /* Remove the waiter from the semaphore */
611   silc_mutex_lock(lock);
612   silc_list_del(fsm->sema->waiters, fsm);
613
614   /* Continue */
615   if (fsm->sema) {
616     silc_fsm_continue(fsm);
617     fsm->sema_timedout = TRUE;
618     fsm->sema = NULL;
619   }
620
621   silc_mutex_unlock(lock);
622 }
623
624 /* Signalled, semaphore */
625
626 SILC_TASK_CALLBACK(silc_fsm_signal)
627 {
628   SilcFSMSemaPost p = context;
629   SilcMutex lock = p->sema->fsm->u.m.lock;
630
631   /* If the semaphore value has went to zero while we've been waiting this
632      callback, sempahore has been been signalled already.  It can happen
633      when using real threads because the FSM may not be waiting state when
634      the sempahore is posted. */
635   silc_mutex_lock(lock);
636   if (!p->sema->value) {
637     silc_mutex_unlock(lock);
638     silc_fsm_sema_unref(p->sema);
639     silc_free(p);
640     return;
641   }
642   silc_mutex_unlock(lock);
643
644   SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
645                   p->fsm));
646
647   /* Signal */
648   silc_fsm_continue_sync(p->fsm);
649
650   silc_fsm_sema_unref(p->sema);
651   silc_free(p);
652 }
653
654 /* Increase semaphore */
655
656 void silc_fsm_sema_post(SilcFSMSema sema)
657 {
658   SilcFSM fsm;
659   SilcFSMSemaPost p;
660   SilcMutex lock = sema->fsm->u.m.lock;
661
662   SILC_LOG_DEBUG(("Posting semaphore %p", sema));
663
664   silc_mutex_lock(lock);
665
666   sema->value++;
667   silc_list_start(sema->waiters);
668   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
669     if (fsm->sema) {
670       silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
671                                     fsm);
672       fsm->sema = NULL;
673     }
674
675     p = silc_calloc(1, sizeof(*p));
676     if (!p)
677       continue;
678     p->sema = sema;
679     p->fsm = fsm;
680     silc_fsm_sema_ref(sema);
681
682     /* Signal through scheduler.  Wake up destination scheduler in case
683        caller is a real thread. */
684     silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 0);
685     silc_schedule_wakeup(fsm->schedule);
686   }
687
688   silc_mutex_unlock(lock);
689 }
690
691 /* Post thread termination semaphore.  Special function used only to
692    signal thread termination when SILC_FSM_THREAD_WAIT was used. */
693
694 static void silc_fsm_thread_termination_post(SilcFSMSema sema)
695 {
696   SilcFSM fsm;
697   SilcMutex lock = sema->fsm->u.m.lock;
698
699   SILC_LOG_DEBUG(("Post thread terminate semaphore %p", sema));
700
701   silc_mutex_lock(lock);
702
703   silc_list_start(sema->waiters);
704   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
705     /* Signal on thread termination.  Wake up destination scheduler in case
706        caller is a real thread. */
707     silc_list_del(sema->waiters, fsm);
708     silc_fsm_continue(fsm);
709     silc_schedule_wakeup(fsm->schedule);
710   }
711
712   silc_mutex_unlock(lock);
713 }
714
715 /* Real thread */
716
717 static void *silc_fsm_thread(void *context)
718 {
719   SilcFSM fsm = context;
720   SilcSchedule old = fsm->schedule;
721
722   SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
723
724   /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
725      cannot be used in this thread.  Application may still use it if it
726      wants but we use our own. */
727   fsm->schedule = silc_schedule_init(0, old);
728   if (!fsm->schedule)
729     return NULL;
730
731   /* Start the FSM thread */
732   if (!silc_schedule_task_add_timeout(fsm->schedule, 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 }