4d09205db4e9043e5eac1b0679d5c57e2b156701
[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 "silcincludes.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
28 /* Allocate FSM */
29
30 SilcFSM silc_fsm_alloc(void *fsm_context,
31                        SilcFSMDestructor destructor,
32                        void *destructor_context,
33                        SilcSchedule schedule)
34 {
35   SilcFSM fsm;
36
37   fsm = silc_calloc(1, sizeof(*fsm));
38   if (!fsm)
39     return NULL;
40
41   if (!silc_fsm_init(fsm, fsm_context, destructor,
42                      destructor_context, schedule)) {
43     silc_free(fsm);
44     return NULL;
45   }
46
47   return fsm;
48 }
49
50 /* Initialize FSM */
51
52 SilcBool silc_fsm_init(SilcFSM fsm,
53                    void *fsm_context,
54                    SilcFSMDestructor destructor,
55                    void *destructor_context,
56                    SilcSchedule schedule)
57 {
58   if (!schedule)
59     return FALSE;
60
61   fsm->fsm_context = fsm_context;
62   fsm->destructor = destructor;
63   fsm->destructor_context = destructor_context;
64   fsm->schedule = schedule;
65   fsm->thread = FALSE;
66   fsm->async_call = FALSE;
67   fsm->u.m.threads = 0;
68   fsm->u.m.lock = NULL;
69
70   return TRUE;
71 }
72
73 /* Allocate FSM thread.  Internally machine and thread use same context. */
74
75 SilcFSMThread silc_fsm_thread_alloc(SilcFSM fsm,
76                                     void *thread_context,
77                                     SilcFSMThreadDestructor destructor,
78                                     void *destructor_context,
79                                     SilcBool real_thread)
80 {
81   SilcFSMThread thread;
82
83   thread = silc_calloc(1, sizeof(*thread));
84   if (!thread)
85     return NULL;
86
87   if (!silc_fsm_thread_init(thread, fsm, thread_context, destructor,
88                             destructor_context, real_thread)) {
89     silc_free(thread);
90     return NULL;
91   }
92
93   return thread;
94 }
95
96 /* Initialize FSM thread.  Internally machine and thread use same context. */
97
98 SilcBool 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->destructor = (SilcFSMDestructor)destructor;
114   thread->destructor_context = destructor_context;
115   thread->schedule = fsm->schedule;
116   thread->thread = TRUE;
117   thread->async_call = FALSE;
118   thread->real_thread = real_thread;
119   thread->u.t.fsm = fsm;
120
121   /* Add to machine */
122   fsm->u.m.threads++;
123
124   /* Allocate lock for the machine if using real threads. */
125   if (real_thread && !fsm->u.m.lock)
126     if (!silc_mutex_alloc(&fsm->u.m.lock))
127       thread->real_thread = FALSE;
128
129   return TRUE;
130 }
131
132 /* FSM is destroyed through scheduler to make sure that all dying
133    real system threads will have their finish callbacks scheduled before
134    this one (when SILC_FSM_THREAD_WAIT was used). */
135
136 SILC_TASK_CALLBACK(silc_fsm_free_final)
137 {
138   SilcFSM f = context;
139
140 #if defined(SILC_DEBUG)
141   /* Machine must not have active threads */
142   if (!f->thread && f->u.m.threads)
143     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, 1);
161 }
162
163 /* FSM is uninitialized through scheduler to make sure that all dying
164    real system threads will have their finish callbacks scheduled before
165    this one (when SILC_FSM_THREAD_WAIT was used). */
166
167 SILC_TASK_CALLBACK(silc_fsm_uninit_final)
168 {
169   SilcFSM f = context;
170
171 #if defined(SILC_DEBUG)
172   /* Machine must not have active threads */
173   if (!f->thread && f->u.m.threads)
174     assert(f->u.m.threads == 0);
175 #endif /* SILC_DEBUG */
176
177   if (!f->thread && f->u.m.lock)
178     silc_mutex_free(f->u.m.lock);
179
180   if (f->thread && f->u.t.sema)
181     silc_fsm_sema_free(f->u.t.sema);
182 }
183
184 /* Uninitializes FSM */
185
186 void silc_fsm_uninit(void *fsm)
187 {
188   SilcFSM f = fsm;
189   silc_schedule_task_add_timeout(f->schedule, silc_fsm_uninit_final, f, 0, 1);
190 }
191
192 /* Task to start real thread. We start threads through scheduler, not
193    directly in silc_fsm_start. */
194
195 SILC_TASK_CALLBACK(silc_fsm_start_real_thread)
196 {
197   SilcFSM f = context;
198
199 #ifdef SILC_THREADS
200   if (silc_thread_create(silc_fsm_thread, f, FALSE))
201     return;
202 #endif /* SILC_THREADS */
203
204   SILC_LOG_DEBUG(("Could not create real thread, using normal FSM thread"));
205
206   f->real_thread = FALSE;
207   if (f->u.m.lock) {
208     silc_mutex_free(f->u.m.lock);
209     f->u.m.lock = NULL;
210   }
211
212   /* Normal FSM operation */
213   silc_fsm_continue_sync(f);
214 }
215
216 /* Start FSM in the specified state */
217
218 void silc_fsm_start(void *fsm, SilcFSMStateCallback start_state)
219 {
220   SilcFSM f = fsm;
221
222   SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
223
224   f->finished = FALSE;
225   f->next_state = start_state;
226   f->synchronous = FALSE;
227
228   /* Start real threads through scheduler */
229   if (f->thread && f->real_thread) {
230     silc_schedule_task_add_timeout(f->schedule, silc_fsm_start_real_thread,
231                                    f, 0, 1);
232     return;
233   }
234
235   /* Normal FSM operation */
236   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
237 }
238
239 /* Start FSM in the specified state synchronously */
240
241 void silc_fsm_start_sync(void *fsm, SilcFSMStateCallback start_state)
242 {
243   SilcFSM f = fsm;
244
245   SILC_LOG_DEBUG(("Starting %s %p", f->thread ? "thread" : "FSM", fsm));
246
247   f->finished = FALSE;
248   f->next_state = start_state;
249   f->synchronous = TRUE;
250
251   /* Start real threads through scheduler */
252   if (f->thread && f->real_thread) {
253     silc_fsm_start_real_thread(f->schedule,
254                                silc_schedule_get_context(f->schedule),
255                                0, 0, f);
256     return;
257   }
258
259   /* Normal FSM operation */
260   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
261 }
262
263 /* Set next FSM state */
264
265 void silc_fsm_next(void *fsm, SilcFSMStateCallback next_state)
266 {
267   SilcFSM f = fsm;
268   f->next_state = next_state;
269 }
270
271 /* Continue after timeout */
272
273 void silc_fsm_next_later(void *fsm, SilcFSMStateCallback next_state,
274                          SilcUInt32 seconds, SilcUInt32 useconds)
275 {
276   SilcFSM f = fsm;
277   f->next_state = next_state;
278   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f,
279                                  seconds, useconds);
280 }
281
282 /* Continue after callback or async operation */
283
284 void silc_fsm_continue(void *fsm)
285 {
286   SilcFSM f = fsm;
287   silc_schedule_task_add_timeout(f->schedule, silc_fsm_run, f, 0, 1);
288 }
289
290 /* Continue after callback or async operation immediately */
291
292 void silc_fsm_continue_sync(void *fsm)
293 {
294   SilcFSM f = fsm;
295   silc_fsm_run(f->schedule, silc_schedule_get_context(f->schedule), 0, 0, f);
296 }
297
298 /* Return associated scheduler */
299
300 SilcSchedule silc_fsm_get_schedule(void *fsm)
301 {
302   SilcFSM f = fsm;
303   return f->schedule;
304 }
305
306 /* Get context */
307
308 void *silc_fsm_get_context(void *fsm)
309 {
310   SilcFSM f = fsm;
311   return f->fsm_context;
312 }
313
314 /* Set context */
315
316 void silc_fsm_set_context(void *fsm, void *fsm_context)
317 {
318   SilcFSM f = fsm;
319   f->fsm_context = fsm_context;
320 }
321
322 /* Wait for thread to terminate */
323
324 SilcBool silc_fsm_thread_wait(void *fsm, void *thread)
325 {
326   SilcFSM t = thread;
327 #if defined(SILC_DEBUG)
328   assert(t->thread);
329 #endif /* SILC_DEBUG */
330   t->u.t.sema = silc_fsm_sema_alloc(t->u.t.fsm, 0);
331   if (!t->u.t.sema)
332     return FALSE;
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 state */
347   status = fsm->next_state(fsm, fsm->fsm_context);
348
349   switch (status) {
350   case SILC_FSM_CONTINUE:
351     /* Synchronously move to next state */
352     SILC_LOG_DEBUG(("State continue %p", fsm));
353     silc_fsm_run(schedule, app_context, type, fd, context);
354     break;
355
356   case SILC_FSM_WAIT:
357     /* The machine is in hold */
358     SILC_LOG_DEBUG(("State wait %p", fsm));
359     fsm->synchronous = FALSE;
360     break;
361
362   case SILC_FSM_FINISH:
363     /* Finish the state machine */
364     SILC_LOG_DEBUG(("State finish %p", fsm));
365 #if defined(SILC_DEBUG)
366     assert(!fsm->finished);
367 #endif /* SILC_DEBUG */
368     fsm->finished = TRUE;
369
370     /* If we are thread and using real threads, the FSM thread will finish
371        in the main thread, not in the created thread. */
372     if (fsm->thread && fsm->real_thread) {
373       silc_schedule_task_add_timeout(app_context, silc_fsm_finish, fsm, 0, 1);
374       silc_schedule_wakeup(app_context);
375       silc_schedule_stop(fsm->schedule);
376       break;
377     }
378
379     /* Normal FSM operation */
380     if (fsm->synchronous)
381       silc_fsm_finish(fsm->schedule, app_context, 0, 0, fsm);
382     else
383       silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_finish,
384                                      fsm, 0, 1);
385     break;
386   }
387 }
388
389 /* Finishes the FSM.  This is always executed in the main thread, even
390    for FSM threads that were run in real threads. */
391
392 SILC_TASK_CALLBACK(silc_fsm_finish)
393 {
394   SilcFSM fsm = context;
395
396   SILC_LOG_DEBUG(("%s %p, is finished", fsm->thread ? "Thread" : "FSM", fsm));
397
398   fsm->next_state = NULL;
399
400   if (fsm->thread) {
401     /* This is thread, send signal */
402     if (fsm->u.t.sema) {
403       silc_fsm_sema_post(fsm->u.t.sema);
404       silc_fsm_sema_wait(fsm->u.t.sema, fsm->u.t.sema->fsm);
405       silc_fsm_sema_free(fsm->u.t.sema);
406       fsm->u.t.sema = NULL;
407     }
408
409     /* Remove the thread from machine */
410     fsm->u.t.fsm->u.m.threads--;
411
412     /* Call the destructor callback only if the underlaying machine is
413        still valid. */
414     if (fsm->destructor && fsm->u.t.fsm->finished == FALSE)
415       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
416
417   } else {
418     /* Call the destructor callback. */
419     if (fsm->destructor)
420       fsm->destructor(fsm, fsm->fsm_context, fsm->destructor_context);
421   }
422 }
423
424 /* Signalled, semaphore */
425
426 static void silc_fsm_signal(SilcFSM fsm)
427 {
428   SILC_LOG_DEBUG(("Signalled %s %p", fsm->thread ? "thread" : "FSM", fsm));
429
430   /* Continue */
431   silc_fsm_continue(fsm);
432
433   /* Wakeup the destination's scheduler in case the signaller is a
434      real thread. */
435   silc_schedule_wakeup(fsm->schedule);
436 }
437
438 /* Allocate FSM semaphore */
439
440 SilcFSMSema silc_fsm_sema_alloc(SilcFSM fsm, SilcUInt32 value)
441 {
442   SilcFSMSema sema;
443
444   sema = silc_calloc(1, sizeof(*sema));
445   if (!sema)
446     return NULL;
447
448   silc_fsm_sema_init(sema, fsm, value);
449
450   return sema;
451 }
452
453 /* Initializes FSM semaphore */
454
455 void silc_fsm_sema_init(SilcFSMSema sema, SilcFSM fsm, SilcUInt32 value)
456 {
457   SILC_LOG_DEBUG(("Initializing semaphore %p", sema));
458 #if defined(SILC_DEBUG)
459   assert(!fsm->thread);
460 #endif /* SILC_DEBUG */
461   sema->fsm = fsm;
462   silc_list_init(sema->waiters, struct SilcFSMObject, next);
463   sema->value = value;
464 }
465
466 /* Free semaphore */
467
468 void silc_fsm_sema_free(SilcFSMSema sema)
469 {
470 #if defined(SILC_DEBUG)
471   assert(silc_list_count(sema->waiters) == 0);
472 #endif /* SILC_DEBUG */
473   silc_free(sema);
474 }
475
476 /* Wait until semaphore is non-zero. */
477
478 SilcUInt32 silc_fsm_sema_wait(SilcFSMSema sema, void *fsm)
479 {
480   SilcMutex lock = sema->fsm->u.m.lock;
481
482   silc_mutex_lock(lock);
483
484   if (!sema->value) {
485 #if defined(SILC_DEBUG)
486     SilcFSM entry;
487     silc_list_start(sema->waiters);
488     while ((entry = silc_list_get(sema->waiters)) != SILC_LIST_END)
489       assert(entry != fsm);
490 #endif /* SILC_DEBUG */
491
492     SILC_LOG_DEBUG(("Waiting for semaphore %p", sema));
493
494     /* Add the FSM to waiter list */
495     silc_list_add(sema->waiters, fsm);
496     silc_mutex_unlock(lock);
497     return 0;
498   }
499
500   SILC_LOG_DEBUG(("Acquired semaphore %p", sema));
501
502   /* It is possible that this FSM is in the list so remove it */
503   silc_list_del(sema->waiters, fsm);
504   sema->value--;
505   silc_mutex_unlock(lock);
506   return 1;
507 }
508
509 /* Wait util semaphore is non-zero, or timeout occurs. */
510
511 SilcUInt32 silc_fsm_sema_timedwait(SilcFSMSema sema, void *fsm,
512                                    SilcUInt32 seconds, SilcUInt32 useconds)
513 {
514   SilcFSM f = fsm;
515   SilcUInt32 value;
516
517   if (f->sema_timedout) {
518     SILC_LOG_DEBUG(("Semaphore was timedout"));
519     f->sema_timedout = FALSE;
520     return 1;
521   }
522
523   value = silc_fsm_sema_wait(sema, fsm);
524   if (!value) {
525     silc_schedule_task_add_timeout(f->schedule, silc_fsm_sema_timedout,
526                                    f, seconds, useconds);
527     f->sema = sema;
528   }
529
530   return value;
531 }
532
533 /* Semaphore timedout */
534
535 SILC_TASK_CALLBACK(silc_fsm_sema_timedout)
536 {
537   SilcFSM fsm = context;
538   SilcMutex lock = fsm->sema->fsm->u.m.lock;
539
540   SILC_LOG_DEBUG(("Semaphore %p timedout", fsm->sema));
541
542   /* Remove the waiter from the semaphore */
543   silc_mutex_lock(lock);
544   silc_list_del(fsm->sema->waiters, fsm);
545   silc_mutex_unlock(lock);
546
547   fsm->sema = NULL;
548   fsm->sema_timedout = TRUE;
549
550   /* Continue */
551   silc_fsm_continue(fsm);
552 }
553
554 /* Increase semaphore */
555
556 void silc_fsm_sema_post(SilcFSMSema sema)
557 {
558   SilcFSM fsm;
559   SilcMutex lock = sema->fsm->u.m.lock;
560
561   SILC_LOG_DEBUG(("Posting semaphore %p", sema));
562
563   silc_mutex_lock(lock);
564
565   sema->value++;
566   silc_list_start(sema->waiters);
567   while ((fsm = silc_list_get(sema->waiters)) != SILC_LIST_END) {
568     if (fsm->sema) {
569       silc_schedule_task_del_by_all(fsm->schedule, 0, silc_fsm_sema_timedout,
570                                     fsm);
571       fsm->sema = NULL;
572     }
573     silc_fsm_signal(fsm);
574   }
575
576   silc_mutex_unlock(lock);
577 }
578
579 /* Real thread */
580
581 static void *silc_fsm_thread(void *context)
582 {
583   SilcFSM fsm = context;
584   SilcSchedule old = fsm->schedule;
585
586   SILC_LOG_DEBUG(("Starting FSM thread in real thread"));
587
588   /* We allocate new SilcSchedule for the FSM, as the old SilcSchedule
589      cannot be used in this thread.  Application may still use it if it
590      wants but we use our own. */
591   fsm->schedule = silc_schedule_init(0, old);
592   if (!fsm->schedule)
593     return NULL;
594
595   /* Start the FSM thread */
596   if (!silc_schedule_task_add_timeout(fsm->schedule, silc_fsm_run, fsm, 0, 1))
597     return NULL;
598
599   /* Run the scheduler */
600   silc_schedule(fsm->schedule);
601
602   /* Free resources */
603   silc_schedule_uninit(fsm->schedule);
604
605   fsm->schedule = old;
606
607   return NULL;
608 }