/////////////////////////////////////////////////////////////////////////////// // // 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 #include #include #include 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_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 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_