/* * CENA C++ Utilities * * by Stephane Chatty * * Copyright 1992-1995 * Centre d'Etudes de la Navigation Aerienne (CENA) * * timers * * $Id$ * $CurLog$ */ #ifdef __GNUG__ #pragma implementation "Timer.h" #endif #include "Timer.h" #include "Signal.h" #include "List.h" #include #include #include #include #include #include /*!class CcuCoreTimer The implementation of \typ{CcuCoreTimer} is a bit tricky, because it tries to be fast. Here is the basic idea: active timers are stored in a list, sorted by increasing time-out times. When a signal occurs, all timers whose time-out time is smaller than the current time are notified and removed from the list, then they are inserted again. Then, the optimizations. First, we distinguish the first active timer from the others. It is not in the list, but has a pointer of its own. Second, we do not remove timers from the list when they are stopped, but rather flag them as inactive. It means that we have to test a flag when extracting timers from the list. It also means that we can have many occurences of the same timer in the list, only one being in the right place. Even worse, when such a timer is inserted again in the list, there is no way of knowing that the other occurences are wrong: it is the same object, and it no longer flagged as inactive. However, if we remove all encountered inactive timers when inserting one, and if we make sure that a timer is flagged as inactive when we insert it, all wrong occurences before the right one will be removed. !*/ /*?class CcuCoreTimer Timers are a means to manage or one more periodic tasks in a program. The class \typ{CcuCoreTimer} is a base class for all kinds of timers. It provides the basic storing and scheduling of timers, as well as a set of member functions available to the user, but does not provide the actual, system dependent, timing scheme. \typ{CcuCoreTimers} were not designed to be used as is, but only to be derived in order to implement a specific timing scheme. For instance, the class \var{CcuBaseTimer} is derived from it and implements signal-based timers (see next section). Building such derived class is very rare, and it has only be done for timers based on the Unix \com{select} system call, in the Unix Channel library. A timer is created with a period expressed in milliseconds. It will then periodically call the member function \fun{Handle}, which should be redefined in derived classes. Timers are activated at creation time. They are disactivated, but not destroyed, after their last pulse. The member function \fun{Handle} is called when a timer is triggered. It has a time parameter: it is passed the current (absolute) time when the signal was received from the system. However, several timers may be triggered at the same time. If one of them has a slow \fun{Handle}, the time passed to the next ones no more corrresponds to the current time. If you want to know the exact time, and you are not sure of the behaviour of the other timers, it is preferable to use a \typ{CcuTimeStamp}. ?*/ /*?class CcuTimerSet The class \typ{CcuTimerSet} is the data structure used by the class \typ{CcuCoreTimer} to manage a set of timers. Each different implementation of timers should work with one such timer set, which is passed to the constructor of \typ{CcuCoreTimer}. ?*/ #ifdef DOC /*? Create an empty \typ{CcuTimerSet}. ?*/ CcuTimerSet :: CcuTimerSet () { } #endif /* DOC */ /*! Remove and return the first active timer in the active timer list. Timers in that list can actually be inactive. !*/ /*?hidden?*/ CcuCoreTimer* CcuTimerSet :: ExtractNextActive () { CcuCoreTimer* t; /* all entries of OtherTimers are valid pointers, hence t == 0 iff OtherTimers is empty */ #ifndef CPLUS_BUG19 while ((t = OtherTimers.RemoveFirst ()) && (t->StatusFlag != CcuCoreTimer::Active)) #else while ((t = (CcuCoreTimer*) OtherTimers.RemoveFirst ()) && (t->StatusFlag != CcuCoreTimer::Active)) #endif ; return t; } /*! This function is called by the signal handler. The first active timer is expired and scheduled again. Then all the timers whose expiration time is earlier that the current time are removed from the list, expired and scheduled again. The first non-expired timer, if any, is removed from the list and saved as the first active timer. Finally, the alarm is set up to expire at the expiration time of the new first active timer. !*/ /*? This function should be called by the underlying timing scheme when one or more timer has expired. It decides which timers should be triggered, and prepares the set of timers for the next call. ?*/ void CcuCoreTimer :: Fire (CcuTimerSet* s) { CcuCoreTimer*& first = s->FirstTimer; /* If the first timer is not mature yet, ignore this call */ if (!first) return; CcuTimeStamp now; #if defined(sun) && defined(__svr4__) /* add tolerance because Solaris sends signals before the expected time (bug #1164919)*/ if (first->NextDate > now + 5) { #else if (first->NextDate > now) { #endif /* this should never happen, except with bogus Solaris... */ first->SetAlarm (first->NextDate - now); // fprintf (stderr, "%d > %d\n", first->NextDate, now); return; } /* Handle the first timer */ first->Reschedule (); first->Handle (now); /* handle all other expired timers. */ CcuCoreTimer* t; while (t = s->ExtractNextActive ()) { /* Problem : if one Handle () is long, "now" will then be wrong. */ if (t->NextDate > now) break; t->Reschedule (); t->Handle (now); } if (t) { CcuTimeStamp then; t->SetAlarm (t->NextDate - then); } else if (first) { /* we need a valid timer just to be able to call StopAlarm */ /* if first is null, StopAlarm has already been called (?) */ first->StopAlarm (); } first = t; } #ifndef CPLUS_BUG19 /*?hidden?*/ int CcuCoreTimer :: IsInactive (CcuCoreTimer* t) { return (t->StatusFlag != Active); } #else /*?hidden?*/ int CcuCoreTimer :: IsInactive (CcuListItem* t) { return (((CcuCoreTimer*) t)->StatusFlag != Active); } #endif /* CPLUS_BUG19 */ /*? Create a timer with that will time out every \var{period} milliseconds, and at most \var{pulses} times. This timer will be managed by the timer set \var{s}. {\em This constructor is only useful when deriving new timers based on a new timing scheme.} ?*/ CcuCoreTimer :: CcuCoreTimer (Millisecond period, int pulses, CcuTimerSet* s) : MySet (s), StatusFlag (Active), Period (period > 0 ? period : 10), PulsesLeft (pulses) { } /*?nodoc?*/ CcuCoreTimer :: ~CcuCoreTimer () { /* the timer has to be stopped in the derived class */ /* Remove all entries pointing to inactive timers, including this one */ if (MySet) MySet->OtherTimers.Remove (IsInactive, CcuList::All); } /*? Change the period of a timer. The new period will not be taken into account before the next time-out. ?*/ void CcuCoreTimer :: ChangePeriod (Millisecond period) { CcuSignalBlocker b (SigAlrm); Period = period; } /*? Change the number of times a timer will go off before stopping. If the timer is stopped, it is not restarted. ?*/ void CcuCoreTimer :: ChangeNbPulses (int nb) { CcuSignalBlocker b (SigAlrm); PulsesLeft = nb; } /*? Stop this timer if it was running, then start it with its current period. This function can be used to reset a timer to the beginning of a period. ?*/ void CcuCoreTimer :: Restart () { if (PulsesLeft == 0) return; /* this function could be optimized: sometimes SetAlarm is called twice. */ CcuSignalBlocker b (SigAlrm); if (StatusFlag == Active) Stop (); Activate (); } /*! This function sets up a timer that was previously inactive. The timer is inserted in the list of active timers, and if it is the first one, the alarm is updated. !*/ /*?hidden?*/ void CcuCoreTimer :: Activate () { if (!MySet) return; CcuTimeStamp now; NextDate = now + Period; CcuCoreTimer*& first = MySet->FirstTimer; if (!first) { first = this; SetAlarm (Period); } else if (NextDate < first->NextDate ) { SetAlarm (Period); MySet->OtherTimers.Prepend (first); first = this; } else Schedule (NextDate); StatusFlag = Active; } /*?hidden?*/ void CcuCoreTimer :: Reschedule () { if (PulsesLeft == 0) // this should not happen... return; if (PulsesLeft > 0) if (--PulsesLeft == 0) return; Schedule (NextDate + Period); } /*?hidden?*/ void CcuCoreTimer :: Schedule (Millisecond when) { NextDate = when; /* temporarily set status to inactive so that obsolete entries in OtherTimers pointing to this timer can be removed */ StatusFlag = Inactive; CcuListOf & others = MySet->OtherTimers; #ifndef CPLUS_BUG19 CcuListIterOf li (others); CcuListIterOf lj (others); #else CcuListIter li (others); CcuListIter lj (others); #endif while (++li) { /* while we're at it, remove inactive timers from the list */ #ifndef CPLUS_BUG19 CcuCoreTimer* cur = *li; #else CcuCoreTimer* cur = (CcuCoreTimer*) *li; #endif if (cur->StatusFlag != Active) { others.RemoveAfter (lj); li = lj; } else if (cur->NextDate < NextDate) ++lj; else break; } others.InsertAfter (lj, this); StatusFlag = Active; } /*? Stop a timer. This timer will not deliver any signal until \fun{Restart} is called. ?*/ void CcuCoreTimer :: Stop () { if (StatusFlag != Active) return; CcuSignalBlocker b (SigAlrm); CcuCoreTimer*& first = MySet->FirstTimer; StatusFlag = Inactive; /* if this timer was the first active one, find another one to replace it */ if (this == first) { first = MySet->ExtractNextActive (); if (first == 0) StopAlarm (); else { CcuTimeStamp now; first->SetAlarm (first->NextDate - now); } } } /*? Wait for this timer to expire. If it is stopped, return immediately. ?*/ void CcuCoreTimer :: Wait () { if (StatusFlag != Active) return; Millisecond next_date = NextDate; for (;;) { if (wait (0) >= 0) // not an interrupt continue; if (StatusFlag != Active || next_date != NextDate) return; } } #ifdef DOC /*?nextdoc?*/ Millisecond CcuCoreTimer :: GetPeriod () const { } /*? Get the period (resp. the number of pulses to go) of a timer. ?*/ int CcuCoreTimer :: GetNbPulses () const { } /*? Get the status of a timer. The returned value is \var{Active} or \var{Inactive}. ?*/ timer_status CcuCoreTimer :: GetStatus () const { } /*?nextdoc?*/ void CcuCoreTimer :: SetAlarm (Millisecond delay) { } /*? These two functions hold the implementation of the timing scheme. They are pure virtual functions, and have to be redefined to implement a specific scheme. \fun{SetAlarm} should ensure that the function \fun{Fire} will be called in \var{delay} milliseconds from now. \fun{StopAlarm} should disable such calls. ?*/ void CcuCoreTimer :: StopAlarm () { } #endif /* DOC */ /*? This virtual function is called when the timer is triggered. It should be redefined in derived classes to attach a behaviour to a timer. ?*/ void CcuCoreTimer :: Handle (Millisecond) { } /*?class CcuBaseTimer The class \typ{CcuBaseTimer} is provided as a base class for signal-based timers. It comes with a derived class \typ{CcuTimer} that can be used as is. A timer is created with a period expressed in milliseconds. It will then periodically call the member function \fun{Handle}, which should be redefined in derived classes. Please note that signal-based timers use the signal \var{SIGALRM} and the interval timers provided by UNIX. Using them concurrently with system calls such as \fun{sleep} yields unexpected results. ?*/ CcuSignalHandler* CcuBaseTimer::TimeOutHandler = 0; CcuTimerSet* CcuBaseTimer::TimerSet = 0; /*?hidden?*/ void CcuBaseTimer :: ClassInit () { TimeOutHandler = new CcuSignalHandler (SigAlrm, &CcuBaseTimer::HandleSignal); TimerSet = new CcuTimerSet; } /*? Create a timer that will send a signal every \var{period} milliseconds, \var{pulses} times. If \var{pulses} is negative, the timer will send signals forever. Timers are activated at creation time. They are disactivated, but not destroyed, after their last pulse. ?*/ CcuBaseTimer :: CcuBaseTimer (Millisecond period, int pulses) : CcuCoreTimer (period, pulses, (TimerSet ? TimerSet : (ClassInit (), TimerSet))) { CcuSignalBlocker b (SigAlrm); if (PulsesLeft != 0) Activate (); } /*?nodoc?*/ CcuBaseTimer :: ~CcuBaseTimer () { /* stop it */ if (StatusFlag == Active) Stop (); } /*?hidden?*/ void CcuBaseTimer :: StopAlarm () { struct itimerval itval; timerclear (&itval.it_value); timerclear (&itval.it_interval); setitimer (ITIMER_REAL, &itval, 0); } /*?hidden?*/ void CcuBaseTimer :: SetAlarm (Millisecond delay) { if (delay <= 0) { StopAlarm (); kill (getpid (), SigAlrm); return; } struct itimerval itval; timerclear (&itval.it_interval); itval.it_value.tv_sec = delay / 1000; itval.it_value.tv_usec = 1000 * (delay % 1000); if (setitimer (ITIMER_REAL, &itval, 0) < 0) fprintf (stderr, "setitimer failed for interval %d\n", delay); } /*! This function is called by the signal handler. The first active timer is expired and scheduled again. Then all the timers whose expiration time is earlier that the current time are removed from the list, expired and scheduled again. The first non-expired timer, if any, is removed from the list and saved as the first active timer. Finally, the alarm is set up to expire at the expiration time of the new first active timer. !*/ /*?hidden?*/ void CcuBaseTimer :: HandleSignal (int) { Fire (TimerSet); } /*?class CcuTimer The class \typ{CcuTimer} is a derived class of \typ{CcuBaseTimer} that can be used without deriving a new class. Each \typ{CcuTimer} holds a pointer to a function which is called when the timer expires. This function, which is passed to the constructor, must take a \typ{Millisecond} argument and return \typ{void}. ?*/ /*? Create a timer that will expire every \var{period} milliseconds and call the function \var{handler}. ?*/ CcuTimer :: CcuTimer (Millisecond period, void (*handler) (Millisecond), int pulses) : CcuBaseTimer (period, pulses), Handler (handler) { } /*?nodoc?*/ CcuTimer :: ~CcuTimer () { } /*?hidden?*/ void CcuTimer :: Handle (Millisecond ref) { (*Handler) (ref); } /*? Change the handling function of a timer. ?*/ #ifdef DOC void CcuTimer :: SetHandler ((void)(*h)(Millisecond)) { } #endif