summaryrefslogtreecommitdiff
path: root/lib/common/Timer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common/Timer.cpp')
-rw-r--r--lib/common/Timer.cpp308
1 files changed, 270 insertions, 38 deletions
diff --git a/lib/common/Timer.cpp b/lib/common/Timer.cpp
index 16133ecc..137ad45f 100644
--- a/lib/common/Timer.cpp
+++ b/lib/common/Timer.cpp
@@ -8,9 +8,14 @@
//
// --------------------------------------------------------------------------
+#ifdef WIN32
+ #define _WIN32_WINNT 0x0500
+#endif
+
#include "Box.h"
#include <signal.h>
+#include <cstring>
#include "Timer.h"
#include "Logging.h"
@@ -20,6 +25,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 +43,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 +78,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 +153,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 +212,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 +232,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 +247,12 @@ void Timers::Reschedule()
}
else
{
+ /*
BOX_TRACE("timer " << *i << " has not "
"expired, triggering in " <<
FORMAT_MICROSECONDS(timeToExpiry) <<
" seconds");
+ */
}
}
}
@@ -233,6 +261,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 +269,7 @@ void Timers::Reschedule()
Timer& rTimer = **i;
int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow;
+ ASSERT(timeToExpiry > 0)
if (timeToExpiry <= 0)
{
timeToExpiry = 1;
@@ -248,23 +278,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 +322,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
+ #ifndef BOX_RELEASE_BUILD
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, false, true));
}
#endif
@@ -310,37 +368,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");
+ #ifndef BOX_RELEASE_BUILD
+ 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
+ #ifndef BOX_RELEASE_BUILD
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 +527,100 @@ 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
+ #ifndef BOX_RELEASE_BUILD
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;
+ mName = rToCopy.mName;
+
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");
+ #ifndef BOX_RELEASE_BUILD
+ 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