670 lines
16 KiB
C++
670 lines
16 KiB
C++
/*++
|
|
|
|
Copyright (c) 1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
tpstimer.cpp
|
|
|
|
Abstract:
|
|
|
|
Contains Win32 thread pool services timer functions
|
|
|
|
Contents:
|
|
TerminateTimers
|
|
SHCreateTimerQueue
|
|
SHDeleteTimerQueue
|
|
SHSetTimerQueueTimer
|
|
SHChangeTimerQueueTimer
|
|
SHCancelTimerQueueTimer
|
|
(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
|
|
|
|
|
|
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;
|
|
|
|
|
|
// functions
|
|
|
|
|
|
VOID TerminateTimers(VOID)
|
|
/*++
|
|
Routine Description:
|
|
Terminate timer thread and global variables
|
|
--*/
|
|
{
|
|
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)
|
|
/*++
|
|
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); // BUGBUG - error code?
|
|
}
|
|
InterlockedDecrement((LPLONG)&g_ActiveRequests);
|
|
return hResult;
|
|
}
|
|
|
|
|
|
LWSTDAPI_(BOOL) SHDeleteTimerQueue(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); // BUGBUG - correct error code?
|
|
}
|
|
} else {
|
|
SetLastError(ERROR_SHUTDOWN_IN_PROGRESS); // BUGBUG - error code?
|
|
}
|
|
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
|
|
)
|
|
/*++
|
|
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); // BUGBUG - error code?
|
|
}
|
|
InterlockedDecrement((LPLONG)&g_ActiveRequests);
|
|
return hTimer;
|
|
}
|
|
|
|
LWSTDAPI_(BOOL)
|
|
SHChangeTimerQueueTimer(
|
|
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; // BUGBUG - error code?
|
|
|
|
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
|
|
DWORD error = GetLastError();
|
|
|
|
ASSERT(error == ERROR_SUCCESS);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
InterlockedDecrement((LPLONG)&g_ActiveRequests);
|
|
if (error != ERROR_SUCCESS) {
|
|
SetLastError(error);
|
|
}
|
|
return bSuccess;
|
|
}
|
|
|
|
|
|
LWSTDAPI_(BOOL)
|
|
SHCancelTimerQueueTimer(
|
|
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); // BUGBUG - error code?
|
|
}
|
|
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);
|
|
}
|