diff --git a/src/libCom/timer/osiTimer.cpp b/src/libCom/timer/osiTimer.cpp index 9d9f21df3..9b5b1b633 100644 --- a/src/libCom/timer/osiTimer.cpp +++ b/src/libCom/timer/osiTimer.cpp @@ -44,6 +44,14 @@ #define epicsExportSharedSymbols #include "osiTimer.h" +// +// global lock used when moving a timer between timer queues +// +osiMutex osiTimer::mutex; + +// +// default global timer queue +// osiTimerQueue osiDefaultTimerQueue; // @@ -51,7 +59,7 @@ osiTimerQueue osiDefaultTimerQueue; // // create an active timer that will expire in delay seconds // -epicsShareFunc osiTimer::osiTimer (double delay, osiTimerQueue & queueIn) +osiTimer::osiTimer (double delay, osiTimerQueue & queueIn) { this->arm (queueIn, delay); } @@ -61,15 +69,19 @@ epicsShareFunc osiTimer::osiTimer (double delay, osiTimerQueue & queueIn) // // create an inactive timer // -epicsShareFunc osiTimer::osiTimer () : +osiTimer::osiTimer () : curState (osiTimer::stateLimbo), pQueue (0) { } // // osiTimer::~osiTimer() +// NOTE: The osiTimer lock is not applied for cleanup here because we are +// synchronizing properly with the queue, and the creator of this object +// should have removed all knowledge of this object from other threads +// before deleting it. // -epicsShareFunc osiTimer::~osiTimer() +osiTimer::~osiTimer() { this->cleanup (); } @@ -77,21 +89,51 @@ epicsShareFunc osiTimer::~osiTimer() // // osiTimer::cancel () // -epicsShareFunc void osiTimer::cancel () +void osiTimer::cancel () { + this->lock (); this->cleanup (); + this->unlock (); this->destroy (); } +// +// osiTimer::reschedule() +// +// pull this timer out of the queue and reinstall +// it with a new experation time +// +void osiTimer::reschedule (osiTimerQueue & queueIn) +{ + this->reschedule (this->delay(), queueIn); +} + +// +// osiTimer::reschedule() +// +// pull this timer out of the queue ans reinstall +// it with a new experation time +// +void osiTimer::reschedule (double newDelay, osiTimerQueue & queueIn) +{ + this->lock (); + this->cleanup (); + this->arm (queueIn, newDelay); + this->unlock (); +} + // // osiTimer::cleanup () +// NOTE: osiTimer lock is applied externally because we must guarantee +// that the lock virtual function is not called by the destructor // void osiTimer::cleanup () { if (this->pQueue) { + this->pQueue->mutex.lock (); // // signal the timer queue if this - // occurrring during the expire call + // occurring during the expire call // back // if (this == this->pQueue->pExpireTmr) { @@ -99,39 +141,39 @@ void osiTimer::cleanup () } switch (this->curState) { case statePending: - this->pQueue->pending.remove(*this); + this->pQueue->pending.remove (*this); break; case stateExpired: - this->pQueue->expired.remove(*this); + this->pQueue->expired.remove (*this); break; - case stateLimbo: - break; default: - assert(0); + break; } this->pQueue = NULL; this->curState = stateLimbo; - } - else { - assert (this->curState==stateLimbo); + + this->pQueue->mutex.unlock (); } } - // // osiTimer::arm() +// NOTE: The osiTimer lock is properly applied externally to this routine +// when it is needed. // -void osiTimer::arm (osiTimerQueue & queueIn, double initialDelay) +void osiTimer::arm (osiTimerQueue &queueIn, double initialDelay) { # ifdef DEBUG unsigned preemptCount=0u; # endif + queueIn.mutex.lock (); + // // calculate absolute expiration time // - this->exp = osiTime::getCurrent() + initialDelay; - + this->exp = osiTime::getCurrent () + initialDelay; + // // insert into the pending queue // @@ -140,16 +182,16 @@ void osiTimer::arm (osiTimerQueue & queueIn, double initialDelay) // // **** this should use a binary tree ???? // - tsDLIterBD iter = queueIn.pending.last(); + tsDLIterBD iter = queueIn.pending.last (); while (1) { - if (iter==tsDLIterBD::eol()) { + if ( iter == tsDLIterBD::eol () ) { // // add to the beginning of the list // queueIn.pending.push (*this); break; } - if (iter->exp <= this->exp) { + if ( iter->exp <= this->exp ) { // // add after the item found that expires earlier // @@ -164,9 +206,9 @@ void osiTimer::arm (osiTimerQueue & queueIn, double initialDelay) this->curState = osiTimer::statePending; this->pQueue = &queueIn; - + # ifdef DEBUG - this->queue.show(10u); + this->queue.show (10u); # endif # ifdef DEBUG @@ -178,13 +220,16 @@ void osiTimer::arm (osiTimerQueue & queueIn, double initialDelay) printf ("Arm of \"%s\" with delay %f at %lx preempting %u\n", this->name(), initialDelay, (unsigned long)this, preemptCount); # endif - + + queueIn.mutex.unlock (); + + queueIn.event.signal (); } // // osiTimer::again() // -epicsShareFunc bool osiTimer::again() const +bool osiTimer::again () const { // // default is to run the timer only once @@ -195,7 +240,7 @@ epicsShareFunc bool osiTimer::again() const // // osiTimer::destroy() // -epicsShareFunc void osiTimer::destroy() +void osiTimer::destroy () { delete this; } @@ -203,7 +248,7 @@ epicsShareFunc void osiTimer::destroy() // // osiTimer::delay() // -epicsShareFunc double osiTimer::delay() const +double osiTimer::delay () const { # ifdef noExceptionsFromCXX assert (0); @@ -213,10 +258,9 @@ epicsShareFunc double osiTimer::delay() const return DBL_MAX; } -epicsShareFunc void osiTimer::show (unsigned level) const +void osiTimer::show (unsigned level) const { - osiTime cur(osiTime::getCurrent()); - + osiTime cur = osiTime::getCurrent (); printf ("osiTimer at %p for \"%s\" with again = %d\n", this, this->name(), this->again()); if (level>=1u) { @@ -227,16 +271,110 @@ epicsShareFunc void osiTimer::show (unsigned level) const } // -// osiTimerQueue::delayToFirstExpire() +// osiTimer::name() +// virtual base default // -double osiTimerQueue::delayToFirstExpire() const +const char *osiTimer::name() const +{ + return "osiTimer"; +} + +// +// osiTimer::timeRemaining() +// +// return the number of seconds remaining before +// this timer will expire +// +double osiTimer::timeRemaining () const +{ + double remaining = this->exp - osiTime::getCurrent(); + if (remaining>0.0) { + return remaining; + } + else { + return 0.0; + } +} + +// +// osiTimer::lock () +// (defaults to one global lock for all timers) +// +void osiTimer::lock () const +{ + this->mutex.lock (); +} + +// +// osiTimer::unlock () +// (defaults to one global lock for all timers) +// +void osiTimer::unlock () const +{ + this->mutex.unlock (); +} + +// +// osiTimerQueue::osiTimerQueue () +// +osiTimerQueue::osiTimerQueue (unsigned managerThreadPriority) : + osiThread ("osiTimerQueue", threadGetStackSize (threadStackMedium), managerThreadPriority), + inProcess (false), pExpireTmr (0), terminateFlag (false) +{ +} + +// +// osiTimerQueue::~osiTimerQueue() +// +osiTimerQueue::~osiTimerQueue() +{ + osiTimer *pTmr; + + this->mutex.lock (); + + // + // destroy any unexpired timers + // + while ( ( pTmr = this->pending.get () ) ) { + pTmr->curState = osiTimer::stateLimbo; + pTmr->destroy (); + } + + // + // destroy any expired timers + // + while ( (pTmr = this->expired.get()) ) { + pTmr->curState = osiTimer::stateLimbo; + pTmr->destroy (); + } + this->terminateFlag = true; + this->event.signal (); +} + +// +// osiTimerQueue::entryPoint () +// +void osiTimerQueue::entryPoint () +{ + while (!this->terminateFlag) { + this->process (); + this->event.wait ( this->delayToFirstExpire () ); + } +} + +// +// osiTimerQueue::delayToFirstExpire () +// +double osiTimerQueue::delayToFirstExpire () const { osiTimer *pTmr; double delay; - pTmr = this->pending.first(); + this->mutex.lock (); + + pTmr = this->pending.first (); if (pTmr) { - delay = pTmr->timeRemaining(); + delay = pTmr->timeRemaining (); } else { // @@ -244,6 +382,9 @@ double osiTimerQueue::delayToFirstExpire() const // delay = 30u * osiTime::secPerMin; } + + this->mutex.unlock (); + #ifdef DEBUG printf ("delay to first item on the queue %f\n", (double) delay); #endif @@ -253,19 +394,23 @@ double osiTimerQueue::delayToFirstExpire() const // // osiTimerQueue::process() // -void osiTimerQueue::process() +void osiTimerQueue::process () { - osiTime cur(osiTime::getCurrent()); + osiTime cur (osiTime::getCurrent()); osiTimer *pTmr; + this->mutex.lock (); + // no recursion if (this->inProcess) { + this->mutex.unlock (); return; } this->inProcess = true; - tsDLIterBD iter = this->pending.first(); - while ( iter!=tsDLIterBD::eol() ) { + + tsDLIterBD iter = this->pending.first (); + while ( iter != tsDLIterBD::eol () ) { if (iter->exp >= cur) { break; } @@ -281,7 +426,7 @@ void osiTimerQueue::process() // I am careful to prevent problems if they access the // above list while in an "expire()" call back // - while ( (pTmr = this->expired.get()) ) { + while ( ( pTmr = this->expired.get () ) ) { pTmr->curState = osiTimer::stateLimbo; @@ -298,8 +443,8 @@ void osiTimerQueue::process() // this->pExpireTmr = pTmr; pTmr->expire(); - if (this->pExpireTmr == pTmr) { - if (pTmr->again()) { + if ( this->pExpireTmr == pTmr ) { + if ( pTmr->again () ) { pTmr->arm (*pTmr->pQueue, pTmr->delay()); } else { @@ -315,6 +460,8 @@ void osiTimerQueue::process() } } this->inProcess = false; + + this->mutex.unlock(); } // @@ -322,6 +469,7 @@ void osiTimerQueue::process() // void osiTimerQueue::show(unsigned level) const { + this->mutex.lock(); printf("osiTimerQueue with %u items pending and %u items expired\n", this->pending.count(), this->expired.count()); tsDLIterBD iter(this->pending.first()); @@ -329,78 +477,85 @@ void osiTimerQueue::show(unsigned level) const iter->show(level); ++iter; } + this->mutex.unlock(); } -// -// osiTimerQueue::~osiTimerQueue() -// -osiTimerQueue::~osiTimerQueue() +extern "C" epicsShareFunc osiTimerQueueId epicsShareAPI osiTimerQueueCreate (unsigned managerThreadPriority) { - osiTimer *pTmr; - - // - // destroy any unexpired timers - // - while ( (pTmr = this->pending.get()) ) { - pTmr->curState = osiTimer::stateLimbo; - pTmr->destroy(); - } - - // - // destroy any expired timers - // - while ( (pTmr = this->expired.get()) ) { - pTmr->curState = osiTimer::stateLimbo; - pTmr->destroy(); - } + return (osiTimerQueueId) new osiTimerQueue (managerThreadPriority); } -// -// osiTimer::name() -// virtual base default -// -epicsShareFunc const char *osiTimer::name() const +class osiTimerForC : public osiTimer { + epicsShareFunc virtual void expire(); + epicsShareFunc virtual void destroy(); + epicsShareFunc virtual bool again() const; + epicsShareFunc virtual double delay() const; + epicsShareFunc virtual void show (unsigned level) const; + const osiTimerJumpTable &jt; + void * pPrivate; +public: + osiTimerForC (const osiTimerJumpTable &jtIn, void *pPrivateIn); +}; + +osiTimerForC::osiTimerForC (const osiTimerJumpTable &jtIn, void *pPrivateIn) : + jt (jtIn), pPrivate (pPrivateIn) {} + +void osiTimerForC::expire () { - return "osiTimer"; + (*this->jt.expire) (this->pPrivate); } -// -// osiTimer::reschedule() -// -// pull this timer out of the queue and reinstall -// it with a new experation time -// -epicsShareFunc void osiTimer::reschedule (osiTimerQueue & queueIn) +void osiTimerForC::destroy () { - this->reschedule (this->delay(), queueIn); + (*this->jt.destroy) (this->pPrivate); } -// -// osiTimer::reschedule() -// -// pull this timer out of the queue ans reinstall -// it with a new experation time -// -epicsShareFunc void osiTimer::reschedule (double newDelay, osiTimerQueue & queueIn) +bool osiTimerForC::again () const { - this->cleanup (); - this->arm (queueIn, newDelay); + return (*this->jt.again) (this->pPrivate) ? true : false ; } -// -// osiTimer::timeRemaining() -// -// return the number of seconds remaining before -// this timer will expire -// -epicsShareFunc double osiTimer::timeRemaining () +double osiTimerForC::delay () const { - double remaining = this->exp - osiTime::getCurrent(); - if (remaining>0.0) { - return remaining; - } - else { - return 0.0; - } + return (*this->jt.delay) (this->pPrivate); } +void osiTimerForC::show (unsigned level) const +{ + (*this->jt.show) (this->pPrivate, level); +} + +extern "C" epicsShareFunc osiTimerId epicsShareAPI osiTimerCreate (const osiTimerJumpTable *pjtIn, void *pPrivateIn) +{ + assert (pjtIn); + return (osiTimerId) new osiTimerForC (*pjtIn, pPrivateIn); +} + +extern "C" epicsShareFunc void epicsShareAPI osiTimerArm (osiTimerId tmrIdIn, osiTimerQueueId queueIdIn, double delay) +{ + osiTimerForC *pTmr = static_cast(tmrIdIn); + assert (pTmr); + assert (queueIdIn); + pTmr->reschedule (delay, *static_cast(queueIdIn)); +} + +extern "C" epicsShareFunc void epicsShareAPI osiTimerCancel (osiTimerId tmrIdIn) +{ + osiTimerForC *pTmr = static_cast(tmrIdIn); + assert (pTmr); + pTmr->cancel (); +} + +extern "C" epicsShareFunc double epicsShareAPI osiTimerTimeRemaining (osiTimerId idIn) +{ + osiTimerForC *pTmr = static_cast (idIn); + assert (pTmr); + return pTmr->timeRemaining (); +} + +extern "C" epicsShareFunc TS_STAMP epicsShareAPI osiTimerExpirationDate (osiTimerId idIn) +{ + osiTimerForC *pTmr = static_cast (idIn); + assert (pTmr); + return pTmr->expirationDate (); +} diff --git a/src/libCom/timer/osiTimer.h b/src/libCom/timer/osiTimer.h index 561c169b6..af32af8a8 100644 --- a/src/libCom/timer/osiTimer.h +++ b/src/libCom/timer/osiTimer.h @@ -31,66 +31,68 @@ #ifndef osiTimerHInclude #define osiTimerHInclude -#include "shareLib.h" // reset share lib defines -#include "tsDLList.h" +#include "shareLib.h" /* reset share lib defines */ #include "osiTime.h" +#include "osiThread.h" +#include "osiMutex.h" +#include "osiEvent.h" + +#ifdef __cplusplus + +#include "tsDLList.h" class osiTimerQueue; +class managerThread; epicsShareExtern osiTimerQueue osiDefaultTimerQueue; +class epicsShareClass osiTimer; +template class epicsShareClass tsDLNode; // // osiTimer // -class osiTimer : public tsDLNode { - friend class osiTimerQueue; +class epicsShareClass osiTimer : public tsDLNode { +friend class osiTimerQueue; public: - // - // exceptions - // - class noDelaySpecified {}; + class noDelaySpecified {}; // exception // - // create an active timer that will expire in delay seconds + // create an active timer that will expire delay secods after it is created + // or create an inactive timer respectively // - epicsShareFunc osiTimer (double delay, osiTimerQueue & queueIn = osiDefaultTimerQueue); - - // - // create an inactive timer - // - epicsShareFunc osiTimer (); - - - epicsShareFunc virtual ~osiTimer(); + osiTimer (double delay, osiTimerQueue & queueIn = osiDefaultTimerQueue); + osiTimer (); // - // change the timers expiration to newDelay + // change the timer's expiration to newDelay // seconds after when reschedule() is called // - epicsShareFunc void reschedule (double newDelay, osiTimerQueue & queueIn = osiDefaultTimerQueue); + void reschedule (double newDelay, osiTimerQueue & queueIn = osiDefaultTimerQueue); // // change the timers expiration to this->delay() // seconds after when reschedule() is called // - epicsShareFunc void reschedule (osiTimerQueue & queueIn = osiDefaultTimerQueue); + void reschedule (osiTimerQueue & queueIn = osiDefaultTimerQueue); // // inactivate the timer and call the virtual destroy() // member function // - epicsShareFunc void cancel (); + void cancel (); // // return the number of seconds remaining before - // this osiTimer will expire + // this osiTimer will expire or the expiration date + // respectively // - epicsShareFunc double timeRemaining(); + double timeRemaining () const; // returns seconds, but inefficent + osiTime expirationDate () const; // efficent // // called when the osiTimer expires // - epicsShareFunc virtual void expire()=0; + virtual void expire () = 0; // // called if @@ -103,7 +105,7 @@ public: // is taking responsibility for freeing (deleting) // timer resources when they are nolonger needed. // - epicsShareFunc virtual void destroy(); + virtual void destroy (); // // returning true indicates that the @@ -113,7 +115,7 @@ public: // the defaut osiTimer::again() returns false // (run the osiTimer once only) // - epicsShareFunc virtual bool again() const; + virtual bool again () const; // // returns the delay prior to expire @@ -125,17 +127,19 @@ public: // not be called unless the again() virtual // function returns true. // - epicsShareFunc virtual double delay() const; + virtual double delay () const; - - epicsShareFunc virtual void show (unsigned level) const; + virtual void show (unsigned level) const; // // for diagnostics // - epicsShareFunc virtual const char *name() const; + virtual const char *name () const; + + virtual ~osiTimer (); private: + enum state {statePending, stateExpired, stateLimbo}; osiTime exp; // experation time @@ -145,33 +149,79 @@ private: // // place osiTimer in the pending queue // - void arm (osiTimerQueue & queueIn, double initialDelay); + void arm (osiTimerQueue &queueIn, double initialDelay); // // detach from any timer queues // void cleanup (); + + static osiMutex mutex; + + virtual void lock () const; + virtual void unlock () const; }; // // osiTimerQueue // -class osiTimerQueue { +class osiTimerQueue : public osiThread { friend class osiTimer; public: - osiTimerQueue() : inProcess(false), pExpireTmr(0) {}; + osiTimerQueue (unsigned managerThreadPriority = threadPriorityMin); ~osiTimerQueue(); double delayToFirstExpire () const; // returns seconds void process (); void show (unsigned level) const; private: - tsDLList pending; - tsDLList expired; - bool inProcess; + osiMutex mutex; + osiEvent event; + tsDLList pending; + tsDLList expired; osiTimer *pExpireTmr; + bool inProcess; + bool terminateFlag; + virtual void entryPoint (); void install (osiTimer &tmr, double delay); }; +inline osiTime osiTimer::expirationDate () const +{ + return this->exp; +} + +#endif /* ifdef __cplusplus */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * osiTimerJumpTable + */ +typedef struct osiTimerJumpTable { + void (*expire) (void *pPrivate); /* called when timer expires */ + void (*destroy) (void *pPrivate); /* called after expire() when again() is false */ + int (*again) (void *pPrivate); /* rearm after expire when again() is true */ + double (*delay) (void *pPrivate); /* rearm delay */ + void (*show) (void *pPrivate, unsigned level); /* diagnostic */ +}osiTimerJumpTable; + +typedef void * osiTimerQueueId; +/* see osiThread.h for the range of priorities allowed here */ +epicsShareFunc osiTimerQueueId epicsShareAPI osiTimerQueueCreate (unsigned managerThreadPriority); + +typedef void * osiTimerId; +epicsShareFunc osiTimerId epicsShareAPI osiTimerCreate (const osiTimerJumpTable *, void *pPrivate); +epicsShareFunc void epicsShareAPI osiTimerArm (osiTimerId, osiTimerQueueId, double delay); +epicsShareFunc void epicsShareAPI osiTimerCancel (osiTimerId); +epicsShareFunc double epicsShareAPI osiTimerTimeRemaining (osiTimerId); +epicsShareFunc TS_STAMP epicsShareAPI osiTimerExpirationDate (osiTimerId); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + #endif // osiTimerHInclude