summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Wilson <chris+github@qwirx.com>2008-07-27 20:36:14 +0000
committerChris Wilson <chris+github@qwirx.com>2008-07-27 20:36:14 +0000
commitc3bb89f8f49202d2551d6de1b01de9df8c8371d6 (patch)
tree36654c68b7c0dbaa177aecea7edbe6b7ac696ff6
parent8715f827e588bb8cd0ffd555735befc315da2523 (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.cpp296
-rw-r--r--lib/common/Timer.h36
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"