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