diff options
author | Chris Wilson <chris+github@qwirx.com> | 2008-07-27 20:36:14 +0000 |
---|---|---|
committer | Chris Wilson <chris+github@qwirx.com> | 2008-07-27 20:36:14 +0000 |
commit | c3bb89f8f49202d2551d6de1b01de9df8c8371d6 (patch) | |
tree | 36654c68b7c0dbaa177aecea7edbe6b7ac696ff6 | |
parent | 8715f827e588bb8cd0ffd555735befc315da2523 (diff) |
New timer implementation using TimerQueue on Windows to avoid the need
to create and manage a separate thread ourselves.
-rw-r--r-- | lib/common/Timer.cpp | 296 | ||||
-rw-r--r-- | lib/common/Timer.h | 36 |
2 files changed, 282 insertions, 50 deletions
diff --git a/lib/common/Timer.cpp b/lib/common/Timer.cpp index 16133ecc..7147e65f 100644 --- a/lib/common/Timer.cpp +++ b/lib/common/Timer.cpp @@ -8,6 +8,10 @@ // // -------------------------------------------------------------------------- +#ifdef WIN32 + #define _WIN32_WINNT 0x0500 +#endif + #include "Box.h" #include <signal.h> @@ -20,6 +24,9 @@ std::vector<Timer*>* Timers::spTimers = NULL; bool Timers::sRescheduleNeeded = false; +#define TIMER_ID "timer " << mName << " (" << this << ") " +#define TIMER_ID_OF(t) "timer " << (t).GetName() << " (" << &(t) << ") " + typedef void (*sighandler_t)(int); // -------------------------------------------------------------------------- @@ -35,9 +42,7 @@ void Timers::Init() ASSERT(!spTimers); #if defined WIN32 && ! defined PLATFORM_CYGWIN - // no support for signals at all - InitTimer(); - SetTimerHandler(Timers::SignalHandler); + // no init needed #else struct sigaction newact, oldact; newact.sa_handler = Timers::SignalHandler; @@ -72,9 +77,7 @@ void Timers::Cleanup() } #if defined WIN32 && ! defined PLATFORM_CYGWIN - // no support for signals at all - FiniTimer(); - SetTimerHandler(NULL); + // no cleanup needed #else struct itimerval timeout; memset(&timeout, 0, sizeof(timeout)); @@ -149,9 +152,22 @@ void Timers::Remove(Timer& rTimer) Reschedule(); } +void Timers::RequestReschedule() +{ + sRescheduleNeeded = true; +} + +void Timers::RescheduleIfNeeded() +{ + if (sRescheduleNeeded) + { + Reschedule(); + } +} + #define FORMAT_MICROSECONDS(t) \ (int)(t / 1000000) << "." << \ - (int)(t % 1000000) + (int)(t % 1000000) << " seconds" // -------------------------------------------------------------------------- // @@ -195,6 +211,9 @@ void Timers::Reschedule() // we will do it anyway. sRescheduleNeeded = false; +#ifdef WIN32 + // win32 timers need no management +#else box_time_t timeNow = GetCurrentBoxTime(); // scan for, trigger and remove expired timers. Removal requires @@ -212,8 +231,14 @@ void Timers::Reschedule() if (timeToExpiry <= 0) { + /* BOX_TRACE("timer " << *i << " has expired, " "triggering it"); + */ + BOX_TRACE(TIMER_ID_OF(**i) "has expired, " + "triggering " << + FORMAT_MICROSECONDS(-timeToExpiry) << + " late"); rTimer.OnExpire(); spTimers->erase(i); restart = true; @@ -221,10 +246,12 @@ void Timers::Reschedule() } else { + /* BOX_TRACE("timer " << *i << " has not " "expired, triggering in " << FORMAT_MICROSECONDS(timeToExpiry) << " seconds"); + */ } } } @@ -233,6 +260,7 @@ void Timers::Reschedule() // Scan to find the next one to fire (earliest deadline). int64_t timeToNextEvent = 0; + std::string nameOfNextEvent; for (std::vector<Timer*>::iterator i = spTimers->begin(); i != spTimers->end(); i++) @@ -240,6 +268,7 @@ void Timers::Reschedule() Timer& rTimer = **i; int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow; + ASSERT(timeToExpiry > 0) if (timeToExpiry <= 0) { timeToExpiry = 1; @@ -248,23 +277,36 @@ void Timers::Reschedule() if (timeToNextEvent == 0 || timeToNextEvent > timeToExpiry) { timeToNextEvent = timeToExpiry; + nameOfNextEvent = rTimer.GetName(); } } ASSERT(timeToNextEvent >= 0); - + + if (timeToNextEvent == 0) + { + BOX_TRACE("timer: no more events, going to sleep."); + } + else + { + BOX_TRACE("timer: next event: " << nameOfNextEvent << + " expires in " << FORMAT_MICROSECONDS(timeToNextEvent)); + } + struct itimerval timeout; memset(&timeout, 0, sizeof(timeout)); timeout.it_value.tv_sec = BoxTimeToSeconds(timeToNextEvent); timeout.it_value.tv_usec = (int) - (BoxTimeToMicroSeconds(timeToNextEvent) % MICRO_SEC_IN_SEC); + (BoxTimeToMicroSeconds(timeToNextEvent) + % MICRO_SEC_IN_SEC); if(::setitimer(ITIMER_REAL, &timeout, NULL) != 0) { - BOX_ERROR("Failed to initialise timer\n"); + BOX_ERROR("Failed to initialise system timer\n"); THROW_EXCEPTION(CommonException, Internal) } +#endif } // -------------------------------------------------------------------------- @@ -279,27 +321,42 @@ void Timers::Reschedule() // Created: 5/11/2006 // // -------------------------------------------------------------------------- -void Timers::SignalHandler(int iUnused) +void Timers::SignalHandler(int unused) { // ASSERT(spTimers); Timers::RequestReschedule(); } -Timer::Timer(size_t timeoutSecs) +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Timer(size_t timeoutSecs, +// const std::string& rName) +// Purpose: Standard timer constructor, takes a timeout in +// seconds from now, and an optional name for +// logging purposes. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer::Timer(size_t timeoutSecs, const std::string& rName) : mExpires(GetCurrentBoxTime() + SecondsToBoxTime(timeoutSecs)), - mExpired(false) + mExpired(false), + mName(rName) +#ifdef WIN32 +, mTimerHandle(INVALID_HANDLE_VALUE) +#endif { #ifndef NDEBUG if (timeoutSecs == 0) { - BOX_TRACE("timer " << this << " initialised for " << - timeoutSecs << " secs, will not fire"); + BOX_TRACE(TIMER_ID "initialised for " << timeoutSecs << + " secs, will not fire"); } else { - BOX_TRACE("timer " << this << " initialised for " << - timeoutSecs << " secs, to fire at " << - FORMAT_MICROSECONDS(mExpires)); + BOX_TRACE(TIMER_ID "initialised for " << timeoutSecs << + " secs, to fire at " << FormatTime(mExpires, true)); } #endif @@ -310,37 +367,157 @@ Timer::Timer(size_t timeoutSecs) else { Timers::Add(*this); + Start(timeoutSecs * MICRO_SEC_IN_SEC_LL); } } +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Start() +// Purpose: This internal function initialises an OS TimerQueue +// timer on Windows, while on Unixes there is only a +// single global timer, managed by the Timers class, +// so this method does nothing. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Start() +{ +#ifdef WIN32 + box_time_t timeNow = GetCurrentBoxTime(); + int64_t timeToExpiry = mExpires - timeNow; + + if (timeToExpiry <= 0) + { + BOX_WARNING(TIMER_ID << "fudging expiry from -" << + FORMAT_MICROSECONDS(-timeToExpiry)) + timeToExpiry = 1; + } + + Start(timeToExpiry); +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Start(int64_t delayInMicros) +// Purpose: This internal function initialises an OS TimerQueue +// timer on Windows, with a specified delay already +// calculated to save us doing it again. Like +// Timer::Start(), on Unixes it does nothing. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Start(int64_t delayInMicros) +{ +#ifdef WIN32 + // only call me once! + ASSERT(mTimerHandle == INVALID_HANDLE_VALUE); + + int64_t delayInMillis = delayInMicros / 1000; + + // Windows XP always seems to fire timers up to 20 ms late, + // at least on my test laptop. Not critical in practice, but our + // tests are precise enough that they will fail if we don't + // correct for it. + delayInMillis -= 20; + + // Set a system timer to call our timer routine + if (CreateTimerQueueTimer(&mTimerHandle, NULL, TimerRoutine, + (PVOID)this, delayInMillis, 0, WT_EXECUTEINTIMERTHREAD) + == FALSE) + { + BOX_ERROR(TIMER_ID "failed to create timer: " << + GetErrorMessage(GetLastError())); + mTimerHandle = INVALID_HANDLE_VALUE; + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Stop() +// Purpose: This internal function deletes the associated OS +// TimerQueue timer on Windows, and on Unixes does +// nothing. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Stop() +{ +#ifdef WIN32 + if (mTimerHandle != INVALID_HANDLE_VALUE) + { + if (DeleteTimerQueueTimer(NULL, mTimerHandle, + INVALID_HANDLE_VALUE) == FALSE) + { + BOX_ERROR(TIMER_ID "failed to delete timer: " << + GetErrorMessage(GetLastError())); + } + mTimerHandle = INVALID_HANDLE_VALUE; + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::~Timer() +// Purpose: Destructor for Timer objects. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + Timer::~Timer() { #ifndef NDEBUG - BOX_TRACE("timer " << this << " destroyed"); + BOX_TRACE(TIMER_ID "destroyed"); #endif Timers::Remove(*this); + Stop(); } +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Timer(Timer& rToCopy) +// Purpose: Copy constructor for Timer objects. Creates a new +// timer that will trigger at the same time as the +// original. The original will usually be discarded. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + Timer::Timer(const Timer& rToCopy) : mExpires(rToCopy.mExpires), - mExpired(rToCopy.mExpired) + mExpired(rToCopy.mExpired), + mName(rToCopy.mName) +#ifdef WIN32 +, mTimerHandle(INVALID_HANDLE_VALUE) +#endif { #ifndef NDEBUG if (mExpired) { - BOX_TRACE("timer " << this << " initialised from timer " << - &rToCopy << ", already expired, will not fire"); + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "already expired, will not fire"); } else if (mExpires == 0) { - BOX_TRACE("timer " << this << " initialised from timer " << - &rToCopy << ", no expiry, will not fire"); + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "no expiry, will not fire"); } else { - BOX_TRACE("timer " << this << " initialised from timer " << - &rToCopy << " to fire at " << + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "to fire at " << (int)(mExpires / 1000000) << "." << (int)(mExpires % 1000000)); } @@ -349,46 +526,99 @@ Timer::Timer(const Timer& rToCopy) if (!mExpired && mExpires != 0) { Timers::Add(*this); + Start(); } } +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::operator=(const Timer& rToCopy) +// Purpose: Assignment operator for Timer objects. Works +// exactly the same as the copy constructor, except +// that if the receiving timer is already running, +// it is stopped first. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + Timer& Timer::operator=(const Timer& rToCopy) { #ifndef NDEBUG if (rToCopy.mExpired) { - BOX_TRACE("timer " << this << " initialised from timer " << - &rToCopy << ", already expired, will not fire"); + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "already expired, will not fire"); } else if (rToCopy.mExpires == 0) { - BOX_TRACE("timer " << this << " initialised from timer " << - &rToCopy << ", no expiry, will not fire"); + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "no expiry, will not fire"); } else { - BOX_TRACE("timer " << this << " initialised from timer " << - &rToCopy << " to fire at " << + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "to fire at " << (int)(rToCopy.mExpires / 1000000) << "." << (int)(rToCopy.mExpires % 1000000)); } #endif Timers::Remove(*this); + Stop(); + mExpires = rToCopy.mExpires; mExpired = rToCopy.mExpired; + if (!mExpired && mExpires != 0) { Timers::Add(*this); + Start(); } + return *this; } +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::OnExpire() +// Purpose: Method called by Timers::Reschedule (on Unixes) +// on next poll after timer expires, or from +// Timer::TimerRoutine (on Windows) from a separate +// thread managed by the OS. Marks the timer as +// expired for future reference. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + void Timer::OnExpire() { #ifndef NDEBUG - BOX_TRACE("timer " << this << " fired"); + BOX_TRACE(TIMER_ID "fired"); #endif mExpired = true; } + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::TimerRoutine(PVOID lpParam, +// BOOLEAN TimerOrWaitFired) +// Purpose: Static method called by the Windows OS when a +// TimerQueue timer expires. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +#ifdef WIN32 +VOID CALLBACK Timer::TimerRoutine(PVOID lpParam, + BOOLEAN TimerOrWaitFired) +{ + Timer* pTimer = (Timer*)lpParam; + pTimer->OnExpire(); + // is it safe to write to write debug output from a timer? + // e.g. to write to the Event Log? +} +#endif diff --git a/lib/common/Timer.h b/lib/common/Timer.h index ba6d71f4..42b2e00f 100644 --- a/lib/common/Timer.h +++ b/lib/common/Timer.h @@ -40,35 +40,24 @@ class Timers static bool sRescheduleNeeded; static void SignalHandler(int iUnused); - + public: static void Init(); static void Cleanup(); static void Add (Timer& rTimer); static void Remove(Timer& rTimer); - static void RequestReschedule() - { - sRescheduleNeeded = true; - } - - static void RescheduleIfNeeded() - { - if (sRescheduleNeeded) - { - Reschedule(); - } - } + static void RequestReschedule(); + static void RescheduleIfNeeded(); }; class Timer { public: - Timer(size_t timeoutSecs); + Timer(size_t timeoutSecs, const std::string& rName = ""); virtual ~Timer(); Timer(const Timer &); Timer &operator=(const Timer &); -public: box_time_t GetExpiryTime() { return mExpires; } virtual void OnExpire(); bool HasExpired() @@ -76,10 +65,23 @@ public: Timers::RescheduleIfNeeded(); return mExpired; } + + const std::string& GetName() const { return mName; } private: - box_time_t mExpires; - bool mExpired; + box_time_t mExpires; + bool mExpired; + std::string mName; + + void Start(); + void Start(int64_t delayInMicros); + void Stop(); + + #ifdef WIN32 + HANDLE mTimerHandle; + static VOID CALLBACK TimerRoutine(PVOID lpParam, + BOOLEAN TimerOrWaitFired); + #endif }; #include "MemLeakFindOff.h" |