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