/* * CENA C++ Utilities * * by Stephane Chatty * * Copyright 1992-1993 * 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. !*/ /*! 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. !*/ /*?hidden?*/ 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 (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) t->SetAlarm (t->NextDate); 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 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. ?*/ CcuCoreTimer :: CcuCoreTimer (Millisecond period, int pulses, CcuTimerSet* set) : MySet (set), StatusFlag (Active), Period (period), PulsesLeft (pulses) { } /*?hidden?*/ 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; } /*? 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 (NextDate); } else if (NextDate < first->NextDate ) { SetAlarm (NextDate); 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 first->SetAlarm (first->NextDate); } } /*? 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; } } /*?hidden?*/ void CcuCoreTimer :: Handle (Millisecond) { } CcuSignalHandler* CcuBaseTimer::TimeOutHandler = 0; CcuTimerSet* CcuBaseTimer::TimerSet = 0; /*?nodoc?*/ void CcuBaseTimer :: ClassInit () { TimeOutHandler = new CcuSignalHandler (SigAlrm, &CcuBaseTimer::HandleSignal); TimerSet = new CcuTimerSet; } CcuBaseTimer :: CcuBaseTimer (Millisecond period, int pulses) : CcuCoreTimer (period, pulses, (TimerSet ? TimerSet : (ClassInit (), TimerSet))) { CcuSignalBlocker b (SigAlrm); if (PulsesLeft != 0) Activate (); } /*?hidden?*/ 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 when) { CcuTimeStamp now; Millisecond delay = when - now; 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); } /*! 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 () { } /*?nodoc?*/ void CcuTimer :: Handle (Millisecond ref) { (*Handler) (ref); }