Added SILC_ASSERT.
[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   SILC_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   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   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   SILC_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   SILC_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     SILC_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_stop(fsm->schedule);
375       silc_schedule_task_add_timeout(app_context, silc_fsm_finish, fsm, 0, 1);
376       silc_schedule_wakeup(app_context);
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   SILC_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 (silc_list_count(sema->waiters) > 0)
470     return;
471   silc_free(sema);
472 }
473
474 /* Reference semaphore */
475
476 static void silc_fsm_sema_ref(SilcFSMSema sema)
477 {
478   sema->refcnt++;
479 }
480
481 /* Unreference semaphore */
482
483 static void silc_fsm_sema_unref(SilcFSMSema sema)
484 {
485   sema->refcnt--;
486   if (sema->refcnt == 0 && sema->allocated)
487     silc_fsm_sema_free(sema);
488 }
489
490 /* Wait until semaphore is non-zero. */
491
492 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
493 {
494   SilcMutex lock = sema->fsm->u.m.lock;
495
496   silc_mutex_lock(lock);
497
498   if (!sema->value) {
499 #if defined(SILC_DEBUG)
500     SilcFSM entry;
501     silc_list_start(sema->waiters);
502     while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
503       SILC_ASSERT(entry != fsm);
504 #endif /* SILC_DEBUG */
505
506     SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
507
508     /* Add the FSM to waiter list */
509     silc_list_add(sema->waiters, fsm);
510     silc_mutex_unlock(lock);
511     return 0;
512   }
513
514   SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
515
516   /* It is possible that this FSM is in the list so remove it */
517   silc_list_del(sema->waiters, fsm);
518   sema->value--;
519   silc_mutex_unlock(lock);
520   return 1;
521 }
522
523 /* Wait util semaphore is non-zero, or timeout occurs. */
524
525 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
526                                    SilcUInt32 seconds, SilcUInt32 useconds,
527                                    SilcBool *ret_to)
528 {
529   SilcMutex lock = sema->fsm->u.m.lock;
530   SilcFSM f = fsm;
531   SilcUInt32 value;
532
533   silc_mutex_lock(lock);
534
535   if (f->sema_timedout) {
536     SILC_LOG_DEBUG(("Semaphore was timedout"));
537     f->sema_timedout = FALSE;
538     if (ret_to)
539       *ret_to = TRUE;
540     silc_mutex_unlock(lock);
541     return 1;
542   }
543
544   silc_mutex_unlock(lock);
545
546   value = silc_fsm_sema_wait(sema, fsm);
547   if (!value) {
548     silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
549                                    f, seconds, useconds);
550     f->sema = sema;
551   }
552
553   if (ret_to)
554     *ret_to = FALSE;
555
556   return value;
557 }
558
559 /* Semaphore timedout */
560
561 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
562 {
563   SilcFSM fsm = context;
564   SilcMutex lock = fsm->sema->fsm->u.m.lock;
565
566   SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
567
568   /* Remove the waiter from the semaphore */
569   silc_mutex_lock(lock);
570   silc_list_del(fsm->sema->waiters, fsm);
571
572   /* Continue */
573   if (fsm->sema) {
574     silc_fsm_continue(fsm);
575     fsm->sema_timedout = TRUE;
576     fsm->sema = NULL;
577   }
578
579   silc_mutex_unlock(lock);
580 }
581
582 /* Signalled, semaphore */
583
584 SILC_TASK_CALLBACK(silc_fsm_signal)
585 {
586   SilcFSMSemaPost p = context;
587   SilcMutex lock = p->sema->fsm->u.m.lock;
588
589   /* If the semaphore value has went to zero while we've been waiting this
590      callback, sempahore has been been signalled already.  It can happen
591      when using real threads because the FSM may not be waiting state when
592      the sempahore is posted. */
593   silc_mutex_lock(lock);
594   if (!p->sema->value) {
595     silc_mutex_unlock(lock);
596     silc_fsm_sema_unref(p->sema);
597     silc_free(p);
598     return;
599   }
600   silc_mutex_unlock(lock);
601
602   SILC_LOG_DEBUG(("Signalled %s %p", p->fsm->thread ? "thread" : "FSM",
603                   p->fsm));
604
605   /* Signal */
606   silc_fsm_continue_sync(p->fsm);
607
608   silc_fsm_sema_unref(p->sema);
609   silc_free(p);
610 }
611
612 /* Increase semaphore */
613
614 void silc_fsm_sema_post(SilcFSMSema sema)
615 {
616   SilcFSM fsm;
617   SilcFSMSemaPost p;
618   SilcMutex lock = sema->fsm->u.m.lock;
619
620   SILC_LOG_DEBUG(("Posting semaphore %p", sema));
621
622   silc_mutex_lock(lock);
623
624   sema->value++;
625   silc_list_start(sema->waiters);
626   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
627     if (fsm->sema) {
628       silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
629                                     fsm);
630       fsm->sema = NULL;
631     }
632
633     p = silc_calloc(1, sizeof(*p));
634     if (!p)
635       continue;
636     p->sema = sema;
637     p->fsm = fsm;
638     silc_fsm_sema_ref(sema);
639
640     /* Signal through scheduler.  Wake up destination scheduler in case
641        caller is a real thread. */
642     silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_signal, p, 0, 1);
643     silc_schedule_wakeup(fsm->schedule);
644   }
645
646   silc_mutex_unlock(lock);
647 }
648
649 /* Post thread termination semaphore.  Special function used only to
650    signal thread termination when SILC_FSM_THREAD_WAIT was used. */
651
652 static void silc_fsm_thread_termination_post(SilcFSMSema sema)
653 {
654   SilcFSM fsm;
655   SilcMutex lock = sema->fsm->u.m.lock;
656
657   SILC_LOG_DEBUG(("Post thread terminate semaphore %p", sema));
658
659   silc_mutex_lock(lock);
660
661   silc_list_start(sema->waiters);
662   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
663     /* Signal on thread termination.  Wake up destination scheduler in case
664        caller is a real thread. */
665     silc_list_del(sema->waiters, fsm);
666     silc_fsm_continue(fsm);
667     silc_schedule_wakeup(fsm->schedule);
668   }
669
670   silc_mutex_unlock(lock);
671 }
672
673 /* Real thread */
674
675 static void *silc_fsm_thread(void *context)
676 {
677   SilcFSM fsm = context;
678   SilcSchedule old = fsm->schedule;
679
680   SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
681
682   /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
683      cannot be used in this thread.  Application may still use it if it
684      wants but we use our own. */
685   fsm->schedule = silc_schedule_init(0, old);
686   if (!fsm->schedule)
687     return NULL;
688
689   /* Start the FSM thread */
690   if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 1))
691     return NULL;
692
693   /* Run the scheduler */
694   silc_schedule(fsm->schedule);
695
696   /* Free resources */
697   silc_schedule_uninit(fsm->schedule);
698
699   fsm->schedule = old;
700
701   return NULL;
702 }