Added SILC Server library.
[silc.git] / lib / silcutil / silcfsm.c
1 /*
2
3   silcfsm.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 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   /* Machine must not have active threads */
141   if (!f->thread && f->u.m.threads)
142     assert(f->u.m.threads == 0);
143 #endif /* SILC_DEBUG */
144
145   if (!f->thread && f->u.m.lock)
146     silc_mutex_free(f->u.m.lock);
147
148   if (f->thread && f->u.t.sema)
149     silc_fsm_sema_free(f->u.t.sema);
150
151   silc_free(f);
152 }
153
154 /* Free FSM */
155
156 void silc_fsm_free(void *fsm)
157 {
158   SilcFSM f = fsm;
159   silc_schedule_task_add_timeout(f->schedule, silc_fsm_free_final, f, 0, 1);
160 }
161
162 /* FSM is uninitialized through scheduler to make sure that all dying
163    real system threads will have their finish callbacks scheduled before
164    this one (when SILC_FSM_THREAD_WAIT was used). */
165
166 SILC_TASK_CALLBACK(silc_fsm_uninit_final)
167 {
168   SilcFSM f = context;
169
170 #if defined(SILC_DEBUG)
171   /* Machine must not have active threads */
172   if (!f->thread && f->u.m.threads)
173     assert(f->u.m.threads == 0);
174 #endif /* SILC_DEBUG */
175
176   if (!f->thread && f->u.m.lock)
177     silc_mutex_free(f->u.m.lock);
178
179   if (f->thread && f->u.t.sema)
180     silc_fsm_sema_free(f->u.t.sema);
181 }
182
183 /* Uninitializes FSM */
184
185 void silc_fsm_uninit(void *fsm)
186 {
187   SilcFSM f = fsm;
188   silc_schedule_task_add_timeout(f->schedule, silc_fsm_uninit_final, f, 0, 1);
189 }
190
191 /* Task to start real thread. We start threads through scheduler, not
192    directly in silc_fsm_start. */
193
194 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
195 {
196   SilcFSM f = context;
197
198 #ifdef SILC_THREADS
199   if (silc_thread_create(silc_fsm_thread, f, FALSE))
200     return;
201 #endif /* SILC_THREADS */
202
203   SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
204
205   /* Normal FSM operation */
206   f->real_thread = FALSE;
207   silc_fsm_continue_sync(f);
208 }
209
210 /* Start FSM in the specified state */
211
212 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
213 {
214   SilcFSM f = fsm;
215
216   SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
217
218   f->finished = FALSE;
219   f->next_state = start_state;
220   f->synchronous = FALSE;
221
222   /* Start real thread through scheduler */
223   if (f->thread && f->real_thread) {
224     silc_schedule_task_add_timeout(f->schedule, silc_fsm_start_real_thread,
225                                    f, 0, 1);
226     return;
227   }
228
229   /* Normal FSM operation */
230   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
231 }
232
233 /* Start FSM in the specified state synchronously */
234
235 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
236 {
237   SilcFSM f = fsm;
238
239   SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
240
241   f->finished = FALSE;
242   f->next_state = start_state;
243   f->synchronous = TRUE;
244
245   /* Start real thread directly */
246   if (f->thread && f->real_thread) {
247     silc_fsm_start_real_thread(f->schedule,
248                                silc_schedule_get_context(f->schedule),
249                                0, 0, f);
250     return;
251   }
252
253   /* Normal FSM operation */
254   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
255 }
256
257 /* Set next FSM state */
258
259 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
260 {
261   SilcFSM f = fsm;
262   f->next_state = next_state;
263 }
264
265 /* Continue after timeout */
266
267 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
268                          SilcUInt32 seconds, SilcUInt32 useconds)
269 {
270   SilcFSM f = fsm;
271   f->next_state = next_state;
272   if (!seconds && !useconds)
273     return;
274   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
275                                  seconds, useconds);
276 }
277
278 /* Continue after callback or async operation */
279
280 void silc_fsm_continue(void *fsm)
281 {
282   SilcFSM f = fsm;
283   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
284 }
285
286 /* Continue after callback or async operation immediately */
287
288 void silc_fsm_continue_sync(void *fsm)
289 {
290   SilcFSM f = fsm;
291   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
292 }
293
294 /* Return associated scheduler */
295
296 SilcSchedule silc_fsm_get_schedule(void *fsm)
297 {
298   SilcFSM f = fsm;
299   return f->schedule;
300 }
301
302 /* Return thread's machine */
303
304 SilcFSM silc_fsm_get_machine(SilcFSMThread thread)
305 {
306   assert(thread->thread);
307   return (SilcFSM)thread->u.t.fsm;
308 }
309
310 /* Set context */
311
312 void silc_fsm_set_context(void *fsm, void *fsm_context)
313 {
314   SilcFSM f = fsm;
315   f->fsm_context = fsm_context;
316 }
317
318 /* Get context */
319
320 void *silc_fsm_get_context(void *fsm)
321 {
322   SilcFSM f = fsm;
323   return f->fsm_context;
324 }
325
326 /* Set state context */
327
328 void silc_fsm_set_state_context(void *fsm, void *state_context)
329 {
330   SilcFSM f = fsm;
331   f->state_context = state_context;
332 }
333
334 /* Get state context */
335
336 void *silc_fsm_get_state_context(void *fsm)
337 {
338   SilcFSM f = fsm;
339   return f->state_context;
340 }
341
342 /* Wait for thread to terminate */
343
344 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
345 {
346   SilcFSM t = thread;
347 #if defined(SILC_DEBUG)
348   assert(t->thread);
349 #endif /* SILC_DEBUG */
350   t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
351   if (!t->u.t.sema)
352     return FALSE;
353   silc_fsm_sema_wait(t->u.t.sema, fsm);
354   return TRUE;
355 }
356
357 /* The machine */
358
359 SILC_TASK_CALLBACK(silc_fsm_run)
360 {
361   SilcFSM fsm = context;
362   SilcFSMStatus status;
363
364   SILC_LOG_DEBUG(("Running %s %p", fsm->thread ? "thread" : "FSM", fsm));
365
366   /* Run the states */
367   do
368     status = fsm->next_state(fsm, fsm->fsm_context, fsm->state_context);
369   while (status == SILC_FSM_CONTINUE);
370
371   switch (status) {
372   case SILC_FSM_WAIT:
373     /* The machine is in hold */
374     SILC_LOG_DEBUG(("State wait %p", fsm));
375     fsm->synchronous = FALSE;
376     break;
377
378   case SILC_FSM_FINISH:
379     /* Finish the state machine */
380     SILC_LOG_DEBUG(("State finish %p", fsm));
381 #if defined(SILC_DEBUG)
382     assert(!fsm->finished);
383 #endif /* SILC_DEBUG */
384     fsm->finished = TRUE;
385
386     /* If we are thread and using real threads, the FSM thread will finish
387        in the main thread, not in the created thread. */
388     if (fsm->thread && fsm->real_thread) {
389       silc_schedule_task_add_timeout(app_context, silc_fsm_finish, fsm, 0, 1);
390       silc_schedule_wakeup(app_context);
391       silc_schedule_stop(fsm->schedule);
392       break;
393     }
394
395     /* Normal FSM operation */
396     if (fsm->synchronous)
397       silc_fsm_finish(fsm->schedule, app_context, 0, 0, fsm);
398     else
399       silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish,
400                                      fsm, 0, 1);
401     break;
402
403   default:
404     break;
405   }
406 }
407
408 /* Finishes the FSM.  This is always executed in the main thread, even
409    for FSM threads that were run in real threads. */
410
411 SILC_TASK_CALLBACK(silc_fsm_finish)
412 {
413   SilcFSM fsm = context;
414
415   SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
416
417   fsm->next_state = NULL;
418
419   if (fsm->thread) {
420     /* This is thread, send signal */
421     if (fsm->u.t.sema) {
422       silc_fsm_thread_termination_post(fsm->u.t.sema);
423       silc_fsm_sema_free(fsm->u.t.sema);
424       fsm->u.t.sema = NULL;
425     }
426
427     /* Remove the thread from machine */
428     fsm->u.t.fsm->u.m.threads--;
429
430     /* Call the destructor callback only if the underlaying machine is
431        still valid. */
432     if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
433       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
434
435   } else {
436     /* Call the destructor callback. */
437     if (fsm->destructor)
438       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
439   }
440 }
441
442 /* Allocate FSM semaphore */
443
444 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
445 {
446   SilcFSMSema sema;
447
448   sema = silc_calloc(1, sizeof(*sema));
449   if (!sema)
450     return NULL;
451
452   silc_fsm_sema_init(sema, fsm, value);
453   sema->allocated = TRUE;
454
455   return sema;
456 }
457
458 /* Initializes FSM semaphore */
459
460 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
461 {
462   SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
463 #if defined(SILC_DEBUG)
464   assert(!fsm->thread);
465 #endif /* SILC_DEBUG */
466   memset(sema, 0, sizeof(*sema));
467   sema->fsm = fsm;
468   sema->refcnt = 0;
469   silc_list_init(sema->waiters, struct SilcFSMObject, next);
470   sema->value = value;
471 }
472
473 /* Free semaphore */
474
475 void silc_fsm_sema_free(SilcFSMSema sema)
476 {
477   if (sema->refcnt > 0)
478     return;
479 #if defined(SILC_DEBUG)
480   assert(silc_list_count(sema->waiters) == 0);
481 #endif /* SILC_DEBUG */
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       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, 1);
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 termination 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, 1))
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   return NULL;
713 }