/*++ Copyright (c) 1998 Microsoft Corporation Module Name: tpstimer.cpp Abstract: Contains Win32 thread pool services timer functions Contents: TerminateTimers SHCreateTimerQueue (IECreateTimerQueue) SHDeleteTimerQueue (IEDeleteTimerQueue) SHSetTimerQueueTimer (IESetTimerQueueTimer) (NTSetTimerQueueTimer) SHChangeTimerQueueTimer (IEChangeTimerQueueTimer) SHCancelTimerQueueTimer (IECancelTimerQueueTimer) (NTCancelTimerQueueTimer) (InitializeTimerThread) (TimerCleanup) (CreateDefaultTimerQueue) (DeleteDefaultTimerQueue) (CleanupDefaultTimerQueue) (TimerThread) (DeleteTimerQueue) (AddTimer) (ChangeTimer) (CancelTimer) Author: Richard L Firth (rfirth) 10-Feb-1998 Environment: Win32 user-mode Notes: Code reworked in C++ from NT-specific C code written by Gurdeep Singh Pall (gurdeep) Revision History: 10-Feb-1998 rfirth Created --*/ #include "priv.h" #include "threads.h" #include "tpsclass.h" #include "tpstimer.h" // // private prototypes // typedef HANDLE (WINAPI * t_CreateTimerQueue)(VOID); typedef BOOL (WINAPI * t_DeleteTimerQueue)(HANDLE); typedef HANDLE (WINAPI * t_SetTimerQueueTimer)(HANDLE, WAITORTIMERCALLBACKFUNC, LPVOID, DWORD, DWORD, LPCSTR, DWORD ); typedef BOOL (WINAPI * t_ChangeTimerQueueTimer)(HANDLE, HANDLE, DWORD, DWORD); typedef BOOL (WINAPI * t_CancelTimerQueueTimer)(HANDLE, HANDLE); // These are KERNEL32 functions that do not match our SHLWAPI APIs typedef BOOL (WINAPI * t_CreateTimerQueueTimer)(PHANDLE, HANDLE, WAITORTIMERCALLBACKFUNC, LPVOID, DWORD, DWORD, ULONG ); typedef BOOL (WINAPI * t_DeleteTimerQueueTimer)(HANDLE, HANDLE, HANDLE); PRIVATE DWORD InitializeTimerThread( VOID ); PRIVATE VOID TimerCleanup( VOID ); PRIVATE HANDLE CreateDefaultTimerQueue( VOID ); PRIVATE VOID DeleteDefaultTimerQueue( VOID ); PRIVATE VOID CleanupDefaultTimerQueue( VOID ); PRIVATE VOID TimerThread( VOID ); PRIVATE VOID DeleteTimerQueue( IN CTimerQueueDeleteRequest * pRequest ); PRIVATE VOID AddTimer( IN CTimerAddRequest * pRequest ); PRIVATE VOID ChangeTimer( IN CTimerChangeRequest * pRequest ); PRIVATE VOID CancelTimer( IN CTimerCancelRequest * pRequest ); // // global data // CTimerQueueList g_TimerQueueList; HANDLE g_hDefaultTimerQueue = NULL; HANDLE g_hTimerThread = NULL; DWORD g_dwTimerId = 0; LONG g_UID = 0; BOOL g_bTimerInit = FALSE; BOOL g_bTimerInitDone = FALSE; BOOL g_bDeferredTimerTermination = FALSE; // // Forward-declared data. // extern t_CreateTimerQueue _I_CreateTimerQueue; extern t_DeleteTimerQueue _I_DeleteTimerQueue; extern t_SetTimerQueueTimer _I_SetTimerQueueTimer; extern t_ChangeTimerQueueTimer _I_ChangeTimerQueueTimer; extern t_CancelTimerQueueTimer _I_CancelTimerQueueTimer; extern t_CreateTimerQueueTimer _I_CreateTimerQueueTimer; extern t_DeleteTimerQueueTimer _I_DeleteTimerQueueTimer; // // Wrappers for NT5 because the Shlwapi version differs slightly from the // NT version. // LWSTDAPI_(HANDLE) NTSetTimerQueueTimer( IN HANDLE hQueue, IN WAITORTIMERCALLBACKFUNC pfnCallback, IN LPVOID pContext, IN DWORD dwDueTime, IN DWORD dwPeriod, IN LPCSTR lpszLibrary OPTIONAL, IN DWORD dwFlags ) { // // Translate the flags from TPS flags to WT flags. // DWORD dwWTFlags = 0; if (dwFlags & TPS_EXECUTEIO) dwWTFlags |= WT_EXECUTEINIOTHREAD; if (dwFlags & TPS_LONGEXECTIME) dwWTFlags |= WT_EXECUTELONGFUNCTION; HANDLE hTimer; if (_I_CreateTimerQueueTimer(&hTimer, hQueue, pfnCallback, pContext, dwDueTime, dwPeriod, dwWTFlags)) { return hTimer; } return NULL; } LWSTDAPI_(BOOL) NTCancelTimerQueueTimer( IN HANDLE hQueue, IN HANDLE hTimer ) { return _I_DeleteTimerQueueTimer(hQueue, hTimer, INVALID_HANDLE_VALUE); } STDAPI_(void) InitTimerQueue() { if (IsOS(OS_WHISTLERORGREATER)) { HMODULE hKernel32 = GetModuleHandle("KERNEL32.DLL"); if (hKernel32) { t_CreateTimerQueue NTCreateTimerQueue; t_DeleteTimerQueue NTDeleteTimerQueue; t_CreateTimerQueueTimer NTCreateTimerQueueTimer; t_ChangeTimerQueueTimer NTChangeTimerQueueTimer; t_DeleteTimerQueueTimer NTDeleteTimerQueueTimer; #define GetKernelProc(fn) \ ((NT##fn = (t_##fn)GetProcAddress(hKernel32, #fn)) != NULL) if (GetKernelProc(CreateTimerQueue) && GetKernelProc(DeleteTimerQueue) && GetKernelProc(CreateTimerQueueTimer) && GetKernelProc(ChangeTimerQueueTimer) && GetKernelProc(DeleteTimerQueueTimer)) { #define SwitchToNTVersion(fn) (_I_##fn = NT##fn) // Redirect the SHLWAPI APIs to the NT versions // (They either point directly to the KERNEL API // or to our stub functions.) SwitchToNTVersion(CreateTimerQueue); SwitchToNTVersion(DeleteTimerQueue); SwitchToNTVersion(ChangeTimerQueueTimer); SwitchToNTVersion(SetTimerQueueTimer); SwitchToNTVersion(CancelTimerQueueTimer); // Save these values so our stub functions can // call the KERNEL API after they do their translation. SwitchToNTVersion(CreateTimerQueueTimer); SwitchToNTVersion(DeleteTimerQueueTimer); } #undef GetKernelProc #undef SwitchToNTVersion } } } // // functions // VOID TerminateTimers( VOID ) /*++ Routine Description: Terminate timer thread and global variables Arguments: None. Return Value: None. --*/ { if (g_bTimerInitDone) { DWORD threadId = GetCurrentThreadId(); if ((g_hTimerThread != NULL) && (threadId != g_dwTimerId)) { QueueNullFunc(g_hTimerThread); DWORD ticks = GetTickCount(); while (g_hTimerThread != NULL) { SleepEx(0, TRUE); if (GetTickCount() - ticks > 10000) { CloseHandle(g_hTimerThread); g_hTimerThread = NULL; break; } } } if (g_dwTimerId == threadId) { g_bDeferredTimerTermination = TRUE; } else { TimerCleanup(); } } } LWSTDAPI_(HANDLE) SHCreateTimerQueue( VOID ) { return _I_CreateTimerQueue(); } LWSTDAPI_(HANDLE) IECreateTimerQueue( VOID ) /*++ Routine Description: Creates a timer queue Arguments: None. Return Value: HANDLE Success - non-NULL pointer to CTimerQueue object Failure - NULL. GetLastError() for more info --*/ { InterlockedIncrement((LPLONG)&g_ActiveRequests); HANDLE hResult = NULL; DWORD error = ERROR_SUCCESS; if (!g_bTpsTerminating) { if (g_hTimerThread == NULL) { error = InitializeTimerThread(); } if (error == ERROR_SUCCESS) { // // timer queue handle is just pointer to timer queue object // hResult = (HANDLE) new CTimerQueue(&g_TimerQueueList); } else { SetLastError(error); } } else { SetLastError(ERROR_SHUTDOWN_IN_PROGRESS); // error code? looks valid -justmann } InterlockedDecrement((LPLONG)&g_ActiveRequests); return hResult; } LWSTDAPI_(BOOL) SHDeleteTimerQueue( IN HANDLE hQueue ) { return _I_DeleteTimerQueue(hQueue); } LWSTDAPI_(BOOL) IEDeleteTimerQueue( IN HANDLE hQueue ) /*++ Routine Description: Deletes the specified timer queue Arguments: hQueue - handle of queue to delete; NULL for default timer queue Return Value: BOOL Success - TRUE Failure - FALSE. Call GetLastError() for more info --*/ { InterlockedIncrement((LPLONG)&g_ActiveRequests); BOOL bSuccess = FALSE; if (!g_bTpsTerminating) { if (hQueue == NULL) { hQueue = g_hDefaultTimerQueue; } if ((hQueue != NULL) && (g_hTimerThread != NULL)) { CTimerQueueDeleteRequest request(hQueue); if (QueueUserAPC((PAPCFUNC)DeleteTimerQueue, g_hTimerThread, (ULONG_PTR)&request)) { request.WaitForCompletion(); bSuccess = request.SetThreadStatus(); } else { #if DBG DWORD error = GetLastError(); ASSERT(error == ERROR_SUCCESS); #endif } } else { SetLastError(ERROR_INVALID_PARAMETER); // correct error code? looks valid -justmann } } else { SetLastError(ERROR_SHUTDOWN_IN_PROGRESS); // error code? looks valid -justmann } InterlockedDecrement((LPLONG)&g_ActiveRequests); return bSuccess; } LWSTDAPI_(HANDLE) SHSetTimerQueueTimer( IN HANDLE hQueue, IN WAITORTIMERCALLBACKFUNC pfnCallback, IN LPVOID pContext, IN DWORD dwDueTime, IN DWORD dwPeriod, IN LPCSTR lpszLibrary OPTIONAL, IN DWORD dwFlags ) { return _I_SetTimerQueueTimer(hQueue, pfnCallback, pContext, dwDueTime, dwPeriod, lpszLibrary, dwFlags); } LWSTDAPI_(HANDLE) IESetTimerQueueTimer( IN HANDLE hQueue, IN WAITORTIMERCALLBACKFUNC pfnCallback, IN LPVOID pContext, IN DWORD dwDueTime, IN DWORD dwPeriod, IN LPCSTR lpszLibrary OPTIONAL, IN DWORD dwFlags ) /*++ Routine Description: Add a timer to a queue Arguments: hQueue - handle of timer queue; NULL for default queue pfnCallback - function to call when timer triggers pContext - parameter to pfnCallback dwDueTime - initial firing time in milliseconds from now dwPeriod - repeating period. 0 for one-shot lpszLibrary - if specified, name of library (DLL) to reference dwFlags - flags controlling function: TPS_EXECUTEIO - Execute callback in I/O thread Return Value: HANDLE Success - non-NULL handle Failure - NULL. Call GetLastError() for more info --*/ { InterlockedIncrement((LPLONG)&g_ActiveRequests); HANDLE hTimer = NULL; if (!g_bTpsTerminating) { DWORD error = ERROR_SUCCESS; if (g_hTimerThread == NULL) { error = InitializeTimerThread(); } ASSERT(g_hTimerThread != NULL); if (error == ERROR_SUCCESS) { if (hQueue == NULL) { hQueue = CreateDefaultTimerQueue(); } if (hQueue != NULL) { CTimerAddRequest * pRequest = new CTimerAddRequest(hQueue, pfnCallback, pContext, dwDueTime, dwPeriod, dwFlags ); if (pRequest != NULL) { hTimer = pRequest->GetHandle(); if (QueueUserAPC((PAPCFUNC)AddTimer, g_hTimerThread, (ULONG_PTR)pRequest )) { } else { #if DBG error = GetLastError(); ASSERT(GetLastError() != ERROR_SUCCESS); #endif delete pRequest; hTimer = NULL; #if DBG SetLastError(error); #endif } } } } else { SetLastError(error); } } else { SetLastError(ERROR_SHUTDOWN_IN_PROGRESS); // error code? looks valid -justmann } InterlockedDecrement((LPLONG)&g_ActiveRequests); return hTimer; } LWSTDAPI_(BOOL) SHChangeTimerQueueTimer( IN HANDLE hQueue, IN HANDLE hTimer, IN DWORD dwDueTime, IN DWORD dwPeriod ) { return _I_ChangeTimerQueueTimer(hQueue, hTimer, dwDueTime, dwPeriod); } LWSTDAPI_(BOOL) IEChangeTimerQueueTimer( IN HANDLE hQueue, IN HANDLE hTimer, IN DWORD dwDueTime, IN DWORD dwPeriod ) /*++ Routine Description: Change the due time or periodicity of a timer Arguments: hQueue - handle of queue on which timer resides. NULL for default queue hTimer - handle of timer to change dwDueTime - new due time dwPeriod - new period Return Value: BOOL Success - TRUE Failure - FALSE. Call GetLastError() for more info --*/ { InterlockedIncrement((LPLONG)&g_ActiveRequests); BOOL bSuccess = FALSE; DWORD error = ERROR_SHUTDOWN_IN_PROGRESS; // error code? looks valid -justmann if (!g_bTpsTerminating) { error = ERROR_OBJECT_NOT_FOUND; if (g_hTimerThread != NULL) { if (hQueue == NULL) { hQueue = g_hDefaultTimerQueue; } if (hQueue != NULL) { CTimerChangeRequest request(hQueue, hTimer, dwDueTime, dwPeriod); error = ERROR_SUCCESS; // both paths call SetLastError() if reqd if (QueueUserAPC((PAPCFUNC)ChangeTimer, g_hTimerThread, (ULONG_PTR)&request )) { request.WaitForCompletion(); bSuccess = request.SetThreadStatus(); } else { #if DBG ASSERT(GetLastError() == ERROR_SUCCESS); #endif } } } } InterlockedDecrement((LPLONG)&g_ActiveRequests); if (error != ERROR_SUCCESS) { SetLastError(error); } return bSuccess; } LWSTDAPI_(BOOL) SHCancelTimerQueueTimer( IN HANDLE hQueue, IN HANDLE hTimer ) { return _I_CancelTimerQueueTimer(hQueue, hTimer); } LWSTDAPI_(BOOL) IECancelTimerQueueTimer( IN HANDLE hQueue, IN HANDLE hTimer ) /*++ Routine Description: Cancels a timer Arguments: hQueue - handle to queue on which timer resides hTimer - handle of timer to cancel Return Value: BOOL Success - TRUE Failure - FALSE. Call GetLastError() for more info --*/ { InterlockedIncrement((LPLONG)&g_ActiveRequests); BOOL bSuccess = FALSE; if (!g_bTpsTerminating) { if (hQueue == NULL) { hQueue = g_hDefaultTimerQueue; } if ((hQueue != NULL) && (g_hTimerThread != NULL)) { CTimerCancelRequest request(hQueue, hTimer); if (QueueUserAPC((PAPCFUNC)CancelTimer, g_hTimerThread, (ULONG_PTR)&request )) { request.WaitForCompletion(); bSuccess = request.SetThreadStatus(); } else { #if DBG DWORD error = GetLastError(); ASSERT(error == ERROR_SUCCESS); #endif } } else { SetLastError(ERROR_INVALID_HANDLE); } } else { SetLastError(ERROR_SHUTDOWN_IN_PROGRESS); // error code? looks valid -justmann } InterlockedDecrement((LPLONG)&g_ActiveRequests); return bSuccess; } // // private functions // PRIVATE DWORD InitializeTimerThread( VOID ) { DWORD error = ERROR_SUCCESS; while (!g_bTimerInitDone) { if (!InterlockedExchange((LPLONG)&g_bTimerInit, TRUE)) { // // N.B. if CTimerQueueList::Init() does anything more than just // initialize lists then add a Deinit() // g_TimerQueueList.Init(); ASSERT(g_hTimerThread == NULL); error = StartThread((LPTHREAD_START_ROUTINE)TimerThread, &g_hTimerThread, FALSE ); if (error == ERROR_SUCCESS) { g_bTimerInitDone = TRUE; } else { InterlockedExchange((LPLONG)&g_bTimerInit, FALSE); } break; } else { SleepEx(0, FALSE); } } return error; } PRIVATE VOID TimerCleanup( VOID ) { while (!g_TimerQueueList.QueueListHead()->IsEmpty()) { CTimerQueueDeleteRequest request((CTimerQueue *) g_TimerQueueList.QueueListHead()->Next()); DeleteTimerQueue(&request); } DeleteDefaultTimerQueue(); g_UID = 0; g_bTimerInit = FALSE; g_bTimerInitDone = FALSE; } BOOL bDefaultQueueInit = FALSE; BOOL bDefaultQueueInitDone = FALSE; BOOL bDefaultQueueInitFailed = FALSE; PRIVATE HANDLE CreateDefaultTimerQueue( VOID ) { do { if ((g_hDefaultTimerQueue != NULL) || bDefaultQueueInitFailed) { return g_hDefaultTimerQueue; } if (!InterlockedExchange((LPLONG)&bDefaultQueueInit, TRUE)) { InterlockedExchange((LPLONG)&bDefaultQueueInitDone, FALSE); g_hDefaultTimerQueue = SHCreateTimerQueue(); if (g_hDefaultTimerQueue == NULL) { bDefaultQueueInitFailed = TRUE; InterlockedExchange((LPLONG)&bDefaultQueueInit, FALSE); } InterlockedExchange((LPLONG)&bDefaultQueueInitDone, TRUE); } else { do { SleepEx(0, FALSE); } while (!bDefaultQueueInitDone); } } while (TRUE); } PRIVATE VOID DeleteDefaultTimerQueue( VOID ) { if (g_hDefaultTimerQueue != NULL) { CTimerQueueDeleteRequest request((CTimerQueue *)g_hDefaultTimerQueue); DeleteTimerQueue(&request); g_hDefaultTimerQueue = NULL; } CleanupDefaultTimerQueue(); } PRIVATE VOID CleanupDefaultTimerQueue( VOID ) { g_hDefaultTimerQueue = NULL; bDefaultQueueInit = FALSE; bDefaultQueueInitDone = FALSE; bDefaultQueueInitFailed = FALSE; } PRIVATE VOID TimerThread( VOID ) { g_dwTimerId = GetCurrentThreadId(); HMODULE hDll = LoadLibrary(g_cszShlwapi); ASSERT(hDll != NULL); ASSERT(g_TpsTls != 0xFFFFFFFF); TlsSetValue(g_TpsTls, (LPVOID)TPS_TIMER_SIGNATURE); while (!g_bTpsTerminating || (g_ActiveRequests != 0)) { if (g_TimerQueueList.Wait()) { if (g_bTpsTerminating && (g_ActiveRequests == 0)) { break; } g_TimerQueueList.ProcessCompletions(); } } ASSERT(g_hTimerThread != NULL); CloseHandle(g_hTimerThread); g_hTimerThread = NULL; if (g_dwTimerId == g_dwTerminationThreadId) { TimerCleanup(); g_bTpsTerminating = FALSE; g_dwTerminationThreadId = 0; g_bDeferredTimerTermination = FALSE; } g_dwTimerId = 0; FreeLibraryAndExitThread(hDll, ERROR_SUCCESS); } PRIVATE VOID DeleteTimerQueue( IN CTimerQueueDeleteRequest * pRequest ) { CTimerQueue * pQueue = (CTimerQueue *)pRequest->GetQueue(); DWORD dwStatus = ERROR_INVALID_PARAMETER; if (g_TimerQueueList.FindQueue((CDoubleLinkedListEntry *)pQueue) != NULL) { pQueue->DeleteTimers(); if (pQueue == g_hDefaultTimerQueue) { CleanupDefaultTimerQueue(); } delete pQueue; dwStatus = ERROR_SUCCESS; } pRequest->SetCompletionStatus(dwStatus); } PRIVATE VOID AddTimer( IN CTimerAddRequest * pRequest ) { CTimerQueue * pQueue = pRequest->GetQueue(); // // add timer object to global list of timer objects, in expiration time // order // pRequest->InsertBack(g_TimerQueueList.TimerListHead()); // // add timer object to end of timer queue list in no particular order. Only // used to delete all objects belonging to queue when queue is deleted // pRequest->TimerListHead()->InsertTail(pQueue->TimerListHead()); pRequest->SetComplete(); } PRIVATE VOID ChangeTimer( IN CTimerChangeRequest * pRequest ) { CTimerQueue * pQueue = (CTimerQueue *)pRequest->GetQueue(); CTimerQueueEntry * pTimer = pQueue->FindTimer(pRequest->GetTimer()); DWORD dwStatus = ERROR_INVALID_PARAMETER; if (pTimer != NULL) { pTimer->SetPeriod(pRequest->GetPeriod()); pTimer->SetExpirationTime(pRequest->GetDueTime()); dwStatus = ERROR_SUCCESS; } pRequest->SetCompletionStatus(dwStatus); } PRIVATE VOID CancelTimer( IN CTimerCancelRequest * pRequest ) { CTimerQueue * pQueue = (CTimerQueue *)pRequest->GetQueue(); CTimerQueueEntry * pTimer = pQueue->FindTimer(pRequest->GetTimer()); DWORD dwStatus = ERROR_INVALID_PARAMETER; if (pTimer != NULL) { if (pTimer->IsInUse()) { pTimer->SetCancelled(); } else { pTimer->Remove(); delete pTimer; } dwStatus = ERROR_SUCCESS; } pRequest->SetCompletionStatus(dwStatus); } // // Definitions of forward-declared data. // t_CreateTimerQueue _I_CreateTimerQueue = IECreateTimerQueue; t_DeleteTimerQueue _I_DeleteTimerQueue = IEDeleteTimerQueue; t_SetTimerQueueTimer _I_SetTimerQueueTimer = IESetTimerQueueTimer; t_ChangeTimerQueueTimer _I_ChangeTimerQueueTimer = IEChangeTimerQueueTimer; t_CancelTimerQueueTimer _I_CancelTimerQueueTimer = IECancelTimerQueueTimer; // // KERNEL functions that our NT stubs use. Not used if in IE mode. // t_CreateTimerQueueTimer _I_CreateTimerQueueTimer = NULL; t_DeleteTimerQueueTimer _I_DeleteTimerQueueTimer = NULL;