Added SILC_FSM_YIELD.
[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 #if defined(SILC_DEBUG)
322   assert(t->thread);
323 #endif /* SILC_DEBUG */
324   t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
325   if (!t->u.t.sema)
326     return FALSE;
327   silc_fsm_sema_wait(t->u.t.sema, fsm);
328   return TRUE;
329 }
330
331 /* The machine */
332
333 SILC_TASK_CALLBACK(silc_fsm_run)
334 {
335   SilcFSM fsm = context;
336   SilcFSMStatus status;
337
338   SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
339
340   /* Run the states */
341   do
342     status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
343   while (status == SILC_FSM_CONTINUE);
344
345   switch (status) {
346   case SILC_FSM_YIELD:
347     /* Continue through scheduler */
348     silc_fsm_continue(fsm);
349     break;
350
351   case SILC_FSM_WAIT:
352     /* The machine is in hold */
353     SILC_LOG_DEBUG(("State wait %p", fsm));
354     fsm->synchronous = FALSE;
355     break;
356
357   case SILC_FSM_FINISH:
358     /* Finish the state machine */
359     SILC_LOG_DEBUG(("State finish %p", fsm));
360 #if defined(SILC_DEBUG)
361     assert(!fsm->finished);
362 #endif /* SILC_DEBUG */
363     fsm->finished = TRUE;
364
365     /* If we are thread and using real threads, the FSM thread will finish
366        in the main thread, not in the created thread. */
367     if (fsm->thread && fsm->real_thread) {
368       silc_schedule_task_add_timeout(app_context, silc_fsm_finish, fsm, 0, 1);
369       silc_schedule_wakeup(app_context);
370       silc_schedule_stop(fsm->schedule);
371       break;
372     }
373
374     /* Normal FSM operation */
375     if (fsm->synchronous)
376       silc_fsm_finish(fsm->schedule, app_context, 0, 0, fsm);
377     else
378       silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish,
379                                      fsm, 0, 1);
380     break;
381
382   default:
383     break;
384   }
385 }
386
387 /* Finishes the FSM.  This is always executed in the main thread, even
388    for FSM threads that were run in real threads. */
389
390 SILC_TASK_CALLBACK(silc_fsm_finish)
391 {
392   SilcFSM fsm = context;
393
394   SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
395
396   fsm->next_state = NULL;
397
398   if (fsm->thread) {
399     /* This is thread, send signal */
400     if (fsm->u.t.sema) {
401       silc_fsm_thread_termination_post(fsm->u.t.sema);
402       silc_fsm_sema_free(fsm->u.t.sema);
403       fsm->u.t.sema = NULL;
404     }
405
406     /* Remove the thread from machine */
407     fsm->u.t.fsm->u.m.threads--;
408
409     /* Call the destructor callback only if the underlaying machine is
410        still valid. */
411     if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
412       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
413
414   } else {
415     if (fsm->u.m.lock) {
416       silc_mutex_free(fsm->u.m.lock);
417       fsm->u.m.lock = NULL;
418     }
419
420     /* Call the destructor callback. */
421     if (fsm->destructor)
422       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
423   }
424 }
425
426 /* Allocate FSM semaphore */
427
428 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
429 {
430   SilcFSMSema sema;
431
432   sema = silc_calloc(1, sizeof(*sema));
433   if (!sema)
434     return NULL;
435
436   silc_fsm_sema_init(sema, fsm, value);
437   sema->allocated = TRUE;
438
439   return sema;
440 }
441
442 /* Initializes FSM semaphore */
443
444 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
445 {
446   SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
447 #if defined(SILC_DEBUG)
448   assert(!fsm->thread);
449 #endif /* SILC_DEBUG */
450   memset(sema, 0, sizeof(*sema));
451   sema->fsm = fsm;
452   sema->refcnt = 0;
453   silc_list_init(sema->waiters, struct SilcFSMObject, next);
454   sema->value = value;
455 }
456
457 /* Free semaphore */
458
459 void silc_fsm_sema_free(SilcFSMSema sema)
460 {
461   if (sema->refcnt > 0)
462     return;
463 #if defined(SILC_DEBUG)
464   assert(silc_list_count(sema->waiters) == 0);
465 #endif /* SILC_DEBUG */
466   silc_free(sema);
467 }
468
469 /* Reference semaphore */
470
471 static void silc_fsm_sema_ref(SilcFSMSema sema)
472 {
473   sema->refcnt++;
474 }
475
476 /* Unreference semaphore */
477
478 static void silc_fsm_sema_unref(SilcFSMSema sema)
479 {
480   sema->refcnt--;
481   if (sema->refcnt == 0 && sema->allocated)
482     silc_fsm_sema_free(sema);
483 }
484
485 /* Wait until semaphore is non-zero. */
486
487 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
488 {
489   SilcMutex lock = sema->fsm->u.m.lock;
490
491   silc_mutex_lock(lock);
492
493   if (!sema->value) {
494 #if defined(SILC_DEBUG)
495     SilcFSM entry;
496     silc_list_start(sema->waiters);
497     while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
498       assert(entry != fsm);
499 #endif /* SILC_DEBUG */
500
501     SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
502
503     /* Add the FSM to waiter list */
504     silc_list_add(sema->waiters, fsm);
505     silc_mutex_unlock(lock);
506     return 0;
507   }
508
509   SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
510
511   /* It is possible that this FSM is in the list so remove it */
512   silc_list_del(sema->waiters, fsm);
513   sema->value--;
514   silc_mutex_unlock(lock);
515   return 1;
516 }
517
518 /* Wait util semaphore is non-zero, or timeout occurs. */
519
520 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
521                                    SilcUInt32 seconds, SilcUInt32 useconds,
522                                    SilcBool *ret_to)
523 {
524   SilcMutex lock = sema->fsm->u.m.lock;
525   SilcFSM f = fsm;
526   SilcUInt32 value;
527
528   silc_mutex_lock(lock);
529
530   if (f->sema_timedout) {
531     SILC_LOG_DEBUG(("Semaphore was timedout"));
532     f->sema_timedout = FALSE;
533     if (ret_to)
534       *ret_to = TRUE;
535     silc_mutex_unlock(lock);
536     return 1;
537   }
538
539   silc_mutex_unlock(lock);
540
541   value = silc_fsm_sema_wait(sema, fsm);
542   if (!value) {
543     silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
544                                    f, seconds, useconds);
545     f->sema = sema;
546   }
547
548   if (ret_to)
549     *ret_to = FALSE;
550
551   return value;
552 }
553
554 /* Semaphore timedout */
555
556 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
557 {
558   SilcFSM fsm = context;
559   SilcMutex lock = fsm->sema->fsm->u.m.lock;
560
561   SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
562
563   /* Remove the waiter from the semaphore */
564   silc_mutex_lock(lock);
565   silc_list_del(fsm->sema->waiters, fsm);
566
567   /* Continue */
568   if (fsm->sema) {
569     silc_fsm_continue(fsm);
570     fsm->sema_timedout = TRUE;
571     fsm->sema = NULL;
572   }
573
574   silc_mutex_unlock(lock);
575 }
576
577 /* Signalled, semaphore */
578
579 SILC_TASK_CALLBACK(silc_fsm_signal)
580 {
581   SilcFSMSemaPost p = context;
582   SilcMutex lock = p->sema->fsm->u.m.lock;
583
584   /* If the semaphore value has went to zero while we've been waiting this
585      callback, sempahore has been been signalled already.  It can happen
586      when using real threads because the FSM may not be waiting state when
587      the sempahore is posted. */
588   silc_mutex_lock(lock);
589   if (!p->sema->value) {
590     silc_mutex_unlock(lock);
591     silc_fsm_sema_unref(p->sema);
592     silc_free(p);
593     return;
594   }
595   silc_mutex_unlock(lock);
596
597   SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
598                   p->fsm));
599
600   /* Signal */
601   silc_fsm_continue_sync(p->fsm);
602
603   silc_fsm_sema_unref(p->sema);
604   silc_free(p);
605 }
606
607 /* Increase semaphore */
608
609 void silc_fsm_sema_post(SilcFSMSema sema)
610 {
611   SilcFSM fsm;
612   SilcFSMSemaPost p;
613   SilcMutex lock = sema->fsm->u.m.lock;
614
615   SILC_LOG_DEBUG(("Posting semaphore %p", sema));
616
617   silc_mutex_lock(lock);
618
619   sema->value++;
620   silc_list_start(sema->waiters);
621   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
622     if (fsm->sema) {
623       silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
624                                     fsm);
625       fsm->sema = NULL;
626     }
627
628     p = silc_calloc(1, sizeof(*p));
629     if (!p)
630       continue;
631     p->sema = sema;
632     p->fsm = fsm;
633     silc_fsm_sema_ref(sema);
634
635     /* Signal through scheduler.  Wake up destination scheduler in case
636        caller is a real thread. */
637     silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 1);
638     silc_schedule_wakeup(fsm->schedule);
639   }
640
641   silc_mutex_unlock(lock);
642 }
643
644 /* Post thread termination semaphore.  Special function used only to
645    signal thread termination when SILC_FSM_THREAD_WAIT was used. */
646
647 static void silc_fsm_thread_termination_post(SilcFSMSema sema)
648 {
649   SilcFSM fsm;
650   SilcMutex lock = sema->fsm->u.m.lock;
651
652   SILC_LOG_DEBUG(("Post thread termination semaphore %p", sema));
653
654   silc_mutex_lock(lock);
655
656   silc_list_start(sema->waiters);
657   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
658     /* Signal on thread termination.  Wake up destination scheduler in case
659        caller is a real thread. */
660     silc_list_del(sema->waiters, fsm);
661     silc_fsm_continue(fsm);
662     silc_schedule_wakeup(fsm->schedule);
663   }
664
665   silc_mutex_unlock(lock);
666 }
667
668 /* Real thread */
669
670 static void *silc_fsm_thread(void *context)
671 {
672   SilcFSM fsm = context;
673   SilcSchedule old = fsm->schedule;
674
675   SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
676
677   /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
678      cannot be used in this thread.  Application may still use it if it
679      wants but we use our own. */
680   fsm->schedule = silc_schedule_init(0, old);
681   if (!fsm->schedule)
682     return NULL;
683
684   /* Start the FSM thread */
685   if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 1))
686     return NULL;
687
688   /* Run the scheduler */
689   silc_schedule(fsm->schedule);
690
691   /* Free resources */
692   silc_schedule_uninit(fsm->schedule);
693
694   fsm->schedule = old;
695
696   return NULL;
697 }