323 lines
6.6 KiB
C++
323 lines
6.6 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) 1997, Microsoft Corp. All rights reserved.
|
|
//
|
|
// FILE
|
|
//
|
|
// Timer.h
|
|
//
|
|
// SYNOPSIS
|
|
//
|
|
// This file describes the class TimerQueue.
|
|
//
|
|
// MODIFICATION HISTORY
|
|
//
|
|
// 11/26/1997 Original version.
|
|
// 4/16/1997 Completely clean-up in Finalize().
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef _TIMER_H_
|
|
#define _TIMER_H_
|
|
|
|
#include <guard.h>
|
|
#include <hashtbl.h>
|
|
#include <nocopy.h>
|
|
|
|
#include <set>
|
|
using std::set;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CLASS
|
|
//
|
|
// TimerQueue
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// This class maintains a queue of timers.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
class TimerQueue
|
|
: Guardable, NonCopyable
|
|
{
|
|
public:
|
|
|
|
void Initialize();
|
|
void Finalize();
|
|
|
|
HANDLE Set(DWORD dwMilliseconds, PIAS_CALLBACK pOnExpiry);
|
|
PIAS_CALLBACK Cancel(HANDLE handle);
|
|
|
|
protected:
|
|
|
|
// Utility function to get the system time as a 64-bit integer.
|
|
static DWORDLONG GetCurrentTime()
|
|
{
|
|
FILETIME ft;
|
|
|
|
GetSystemTimeAsFileTime(&ft);
|
|
|
|
return (DWORDLONG)ft.dwHighDateTime << 32 | ft.dwLowDateTime;
|
|
}
|
|
|
|
struct Timer;
|
|
friend struct Timer;
|
|
|
|
// Function class to compare timer pointers.
|
|
struct ByExpiry {
|
|
bool operator()(const Timer* t1, const Timer* t2) const;
|
|
};
|
|
|
|
typedef set<Timer*, ByExpiry> TIMER_LIST;
|
|
typedef TIMER_LIST::iterator ITERATOR;
|
|
|
|
//////////
|
|
// Struct used to represent an individual timer.
|
|
//////////
|
|
struct Timer : NonCopyable
|
|
{
|
|
Timer(DWORD dwMilliseconds, PIAS_CALLBACK pOnExpiry)
|
|
: expiry(GetCurrentTime() + 10000i64 * dwMilliseconds),
|
|
onExpiry(pOnExpiry)
|
|
{ }
|
|
|
|
bool operator<(const Timer& timer) const
|
|
{
|
|
return (expiry < timer.expiry) ||
|
|
(expiry == timer.expiry && handle < timer.handle);
|
|
}
|
|
|
|
IAS_USE_SMALL_BLOCK_POOL();
|
|
|
|
DWORDLONG expiry; // The expiration time.
|
|
DWORD period; // The period of the timer in msec.
|
|
DWORD handle; // Unique ID for the timer.
|
|
PIAS_CALLBACK onExpiry; // Callback triggered when timer expires.
|
|
ITERATOR iterator; // Location of the timer in the timer list.
|
|
};
|
|
|
|
// Function class to extract the handle.
|
|
struct ExtractHandle {
|
|
DWORD operator()(const Timer* t1) const { return t1->handle; }
|
|
};
|
|
|
|
// Function class to hash a handle.
|
|
struct HashHandle {
|
|
DWORD operator()(DWORD handle) const { return handle; }
|
|
};
|
|
|
|
typedef hash_table<DWORD, HashHandle, Timer*, ExtractHandle> HANDLE_MAP;
|
|
|
|
DWORD TimeToNextExpiry() const;
|
|
Timer* GetExpiredTimer();
|
|
void WatchTimers();
|
|
|
|
DWORD nextHandle; // Next handle to assign.
|
|
TIMER_LIST timers; // timers sorted by expiry.
|
|
HANDLE_MAP handleMap; // timers mapped by handle.
|
|
bool done; // Flag to shutdown the watcher thread.
|
|
HANDLE nudge; // Event to knock watcher out of wait.
|
|
HANDLE exited; // Event signalling watcher has exited.
|
|
};
|
|
|
|
|
|
inline bool TimerQueue::ByExpiry::operator()(
|
|
const TimerQueue::Timer* t1,
|
|
const TimerQueue::Timer* t2
|
|
) const
|
|
{
|
|
return *t1 < *t2;
|
|
}
|
|
|
|
|
|
void TimerQueue::Initialize()
|
|
{
|
|
nextHandle = 0;
|
|
done = false;
|
|
nudge = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
exited = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
IASRequestThread(MakeBoundCallback(this, &TimerQueue::WatchTimers));
|
|
}
|
|
|
|
|
|
void TimerQueue::Finalize()
|
|
{
|
|
done = true;
|
|
|
|
SetEvent(nudge);
|
|
|
|
WaitForSingleObject(exited, INFINITE);
|
|
|
|
CloseHandle(exited);
|
|
CloseHandle(nudge);
|
|
|
|
handleMap.clear();
|
|
timers.clear();
|
|
}
|
|
|
|
|
|
HANDLE TimerQueue::Set(DWORD dwMilliseconds, PIAS_CALLBACK pOnExpiry)
|
|
{
|
|
if (!pOnExpiry)
|
|
{
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Timer* timer = new Timer(dwMilliseconds, pOnExpiry);
|
|
|
|
if (timer == NULL)
|
|
{
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Lock();
|
|
|
|
timer->handle = ++nextHandle;
|
|
|
|
// Make sure we never give out zero.
|
|
if (timer->handle == 0) timer->handle = ++nextHandle;
|
|
|
|
// Insert the timer into the queue ...
|
|
timer->iterator = timers.insert(timer).first;
|
|
|
|
// ... and the handle map.
|
|
handleMap.insert(timer);
|
|
|
|
// If this is the next timer to expire, then we need to reset the watcher.
|
|
bool IsNext = (timer == *timers.begin());
|
|
|
|
Unlock();
|
|
|
|
if (IsNext) SetEvent(nudge);
|
|
|
|
// Timers are opaque to the outside world.
|
|
return (HANDLE)timer->handle;
|
|
}
|
|
|
|
|
|
PIAS_CALLBACK TimerQueue::Cancel(HANDLE handle)
|
|
{
|
|
_serialize
|
|
|
|
Timer* const* node = handleMap.find((DWORD)handle);
|
|
|
|
if (!node)
|
|
{
|
|
SetLastError(ERROR_NOT_FOUND);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Timer* timer = *node;
|
|
|
|
if (timer->iterator == timers.end())
|
|
{
|
|
// If the timer is in the handle map, but not the queue, then it must
|
|
// be currently executing.
|
|
|
|
SetLastError(ERROR_IO_PENDING);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
timers.erase(timer->iterator);
|
|
|
|
handleMap.erase((DWORD)handle);
|
|
|
|
PIAS_CALLBACK Callback = timer->onExpiry;
|
|
|
|
delete timer;
|
|
|
|
return Callback;
|
|
}
|
|
|
|
|
|
DWORD TimerQueue::TimeToNextExpiry() const
|
|
{
|
|
//////////
|
|
// NOT SERIALIZED
|
|
//////////
|
|
|
|
if (timers.empty()) return INFINITE;
|
|
|
|
DWORDLONG Now = GetCurrentTime();
|
|
|
|
DWORDLONG Next = (*timers.begin())->expiry;
|
|
|
|
// Has the next timer expired?
|
|
return (Next > Now) ? (Next - Now)/10000 : 0;
|
|
}
|
|
|
|
|
|
TimerQueue::Timer* TimerQueue::GetExpiredTimer()
|
|
{
|
|
// We loop until either the done flag is set or a timer expires.
|
|
while (true)
|
|
{
|
|
if (done) return NULL;
|
|
|
|
Lock();
|
|
|
|
DWORD Timeout = TimeToNextExpiry();
|
|
|
|
if (Timeout == 0) break;
|
|
|
|
Unlock();
|
|
|
|
// The next timer hasn't expired yet, so wait.
|
|
WaitForSingleObject(nudge, Timeout);
|
|
}
|
|
|
|
//////////
|
|
// If we made it here, a timer has expired.
|
|
//////////
|
|
|
|
Timer* timer = *timers.begin();
|
|
|
|
// Remove it from the queue ...
|
|
timers.erase(timer->iterator);
|
|
|
|
// ... and reset the iterator.
|
|
timer->iterator = timers.end();
|
|
|
|
Unlock();
|
|
|
|
return timer;
|
|
}
|
|
|
|
|
|
void TimerQueue::WatchTimers()
|
|
{
|
|
Timer* timer = GetExpiredTimer();
|
|
|
|
if (timer == NULL)
|
|
{
|
|
SetEvent(exited);
|
|
|
|
return;
|
|
}
|
|
|
|
// Get a new watcher thread to take our place.
|
|
IASRequestThread(MakeBoundCallback(this, &TimerQueue::WatchTimers));
|
|
|
|
timer->onExpiry->CallbackRoutine(timer->onExpiry);
|
|
|
|
Lock();
|
|
|
|
handleMap.erase(timer->handle);
|
|
|
|
Unlock();
|
|
|
|
delete timer;
|
|
}
|
|
|
|
|
|
#endif // _TIMER_H_
|