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