[Box Backup-commit] COMMIT r2206 - box/trunk/lib/common

boxbackup-dev@fluffy.co.uk boxbackup-dev@fluffy.co.uk
Sun, 27 Jul 2008 21:36:14 +0100 (BST)


Author: chris
Date: 2008-07-27 21:36:14 +0100 (Sun, 27 Jul 2008)
New Revision: 2206

Modified:
   box/trunk/lib/common/Timer.cpp
   box/trunk/lib/common/Timer.h
Log:
New timer implementation using TimerQueue on Windows to avoid the need
to create and manage a separate thread ourselves.


Modified: box/trunk/lib/common/Timer.cpp
===================================================================
--- box/trunk/lib/common/Timer.cpp	2008-07-27 20:11:25 UTC (rev 2205)
+++ box/trunk/lib/common/Timer.cpp	2008-07-27 20:36:14 UTC (rev 2206)
@@ -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 @@
 	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 @@
 	}
 	
 	#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 @@
 	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 @@
 	// 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 @@
 		
 			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 @@
 			}
 			else
 			{
+				/*
 				BOX_TRACE("timer " << *i << " has not "
 					"expired, triggering in " <<
 					FORMAT_MICROSECONDS(timeToExpiry) <<
 					" seconds");
+				*/
 			}
 		}
 	}
@@ -233,6 +260,7 @@
 	// 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 @@
 		Timer& rTimer = **i;
 		int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow;
 
+		ASSERT(timeToExpiry > 0)
 		if (timeToExpiry <= 0)
 		{
 			timeToExpiry = 1;
@@ -248,23 +277,36 @@
 		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 @@
 //		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 @@
 	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 @@
 	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

Modified: box/trunk/lib/common/Timer.h
===================================================================
--- box/trunk/lib/common/Timer.h	2008-07-27 20:11:25 UTC (rev 2205)
+++ box/trunk/lib/common/Timer.h	2008-07-27 20:36:14 UTC (rev 2206)
@@ -40,35 +40,24 @@
 
 	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 @@
 		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"