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