/* * CENA C++ Utilities * * by Stephane Chatty * * Copyright 1992 * Centre d'Etudes de la Navigation Aerienne (CENA) * * timers * * $Id$ * $CurLog$ */ #include "Timer.h" #include "Signal.h" #include "List.h" #include #include #include #include #include /*?class CcuBaseTimer The class \typ{CcuBaseTimer} is provided as a base class for timers. It comes with a derived class \typ{CcuTimer} that can immediately be used. 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 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. The member function \fun{Handle} 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 CcuBaseTimer The implementation of CcuBaseTimer 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. !*/ CcuSignalHandler* CcuBaseTimer::TimeOutHandler = 0; CcuBaseTimer* CcuBaseTimer::FirstActive = 0; #ifndef CPLUS_BUG19 CcuListOf * CcuBaseTimer::OtherActive = 0; #else CcuList* CcuBaseTimer::OtherActive = 0; #endif /*?nodoc?*/ void CcuBaseTimer :: ClassInit () { TimeOutHandler = new CcuSignalHandler (SigAlrm, &CcuBaseTimer::HandleSignal); #ifndef CPLUS_BUG19 OtherActive = new CcuListOf ; #else OtherActive = new CcuList; #endif } /*?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); setitimer (ITIMER_REAL, &itval, 0); } /*! Remove and return the first active timer in the active timer list. Timers in that list can actually be inactive. !*/ /*?hidden?*/ CcuBaseTimer* CcuBaseTimer :: ExtractNextActive () { CcuBaseTimer* t; /* all entries of OtherActive are valid pointers, hence t == 0 iff OtherActive is empty */ #ifndef CPLUS_BUG19 while ((t = OtherActive->RemoveFirst ()) && (t->Status != Active)) #else while ((t = (CcuBaseTimer*) OtherActive->RemoveFirst ()) && (t->Status != Active)) #endif ; return t; } #ifndef CPLUS_BUG19 /*?hidden?*/ int CcuBaseTimer :: IsInactive (CcuBaseTimer* t) { return (t->Status != Active); } #else /*?hidden?*/ int CcuBaseTimer :: IsInactive (CcuListItem* t) { return (((CcuBaseTimer*) t)->Status != Active); } #endif /* CPLUS_BUG19 */ /*! 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) { CcuTimeStamp now; /* Handle the first timer */ FirstActive->Reschedule (); FirstActive->Handle (now); /* handle all other expired timers. */ CcuBaseTimer* t; while (t = ExtractNextActive ()) { /* Problem : if one Handle () is long, "now" will then be wrong. */ if (t->NextDate > now) break; t->Reschedule (); t->Handle (now); } CcuTimeStamp then; if (t) SetAlarm (t->NextDate - then); FirstActive = t; } /*? 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 signal. ?*/ CcuBaseTimer :: CcuBaseTimer (Millisecond period, int pulses) : Status (Active), Period (period), PulsesLeft (pulses) { CcuSignalBlocker b (SigAlrm); if (!TimeOutHandler) ClassInit (); if (PulsesLeft != 0) Activate (); } /*?hidden?*/ CcuBaseTimer :: ~CcuBaseTimer () { /* stop it */ if (Status == Active) Stop (); /* Remove all entries pointing to inactive timers, including this one */ OtherActive->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 CcuBaseTimer :: ChangePeriod (Millisecond period) { CcuSignalBlocker b (SigAlrm); Period = period; } /*? 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 CcuBaseTimer :: Restart () { if (PulsesLeft == 0) return; /* this function could be optimized: sometimes SetAlarm is called twice. */ CcuSignalBlocker b (SigAlrm); if (Status == 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 CcuBaseTimer :: Activate () { CcuTimeStamp now; NextDate = now + Period; if (!FirstActive) { FirstActive = this; SetAlarm (Period); } else if (NextDate < FirstActive->NextDate ) { SetAlarm (Period); OtherActive->Prepend (FirstActive); FirstActive = this; } else Schedule (NextDate); Status = Active; } /*?hidden?*/ void CcuBaseTimer :: Reschedule () { if (PulsesLeft == 0) // this should not happen... return; if (PulsesLeft > 0) if (--PulsesLeft == 0) return; Schedule (NextDate + Period); } /*?hidden?*/ void CcuBaseTimer :: Schedule (Millisecond when) { NextDate = when; /* temporarily set status to inactive so that obsolete entries in OtherActive pointing to this timer can be removed */ Status = Inactive; #ifndef CPLUS_BUG19 CcuListIterOf li (*OtherActive); CcuListIterOf lj (*OtherActive); #else CcuListIter li (*OtherActive); CcuListIter lj (*OtherActive); #endif while (++li) { /* while we're at it, remove inactive timers from the list */ #ifndef CPLUS_BUG19 CcuBaseTimer* cur = *li; #else CcuBaseTimer* cur = (CcuBaseTimer*) *li; #endif if (cur->Status != Active) { OtherActive->RemoveAfter (lj); li = lj; } else if (cur->NextDate < NextDate) ++lj; else break; } OtherActive->InsertAfter (lj, this); Status = Active; } /*? Stop a timer. This timer will not deliver any signal until \fun{Restart} is called. ?*/ void CcuBaseTimer :: Stop () { if (Status != Active) return; CcuSignalBlocker b (SigAlrm); Status = Inactive; /* if this timer was the first active one, find another one to replace it */ if (this == FirstActive) { FirstActive = ExtractNextActive (); if (FirstActive == 0) StopAlarm (); else { CcuTimeStamp now; SetAlarm (FirstActive->NextDate -now); } } } /*? Wait for this timer to expire. If it is stopped, return immediately. ?*/ void CcuBaseTimer :: Wait () { if (Status != Active) return; Millisecond next_date = NextDate; for (;;) { if (wait (0) >= 0) // not an interrupt continue; if (Status != Active || next_date != NextDate) return; } } /*?hidden?*/ void CcuBaseTimer :: Handle (Millisecond) { } /*?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 () { } /*?nodoc?*/ void CcuTimer :: Handle (Millisecond ref) { (*Handler) (ref); }