Windows2000/private/shell/shlwapi/tpstimer.cpp
2020-09-30 17:12:32 +02:00

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);
}