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