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