706 lines
16 KiB
C
706 lines
16 KiB
C
/*++
|
||
|
||
Copyright (c) 1998 Microsoft Corporation
|
||
|
||
Module Name:
|
||
waittime.c
|
||
|
||
Abstract:
|
||
A timeout list is managed by a thread waiting on a
|
||
waitable timer.
|
||
|
||
The timer can be adjusted without context switching to the
|
||
thread that is waiting on the timer.
|
||
|
||
An entry can be pulled off the list. The timer is adjusted
|
||
if the entry was at the head of the queue.
|
||
|
||
The queue is sorted by timeout value. The timeout value is
|
||
an absolute filetime.
|
||
|
||
The list entry is a command packet. The generic command
|
||
packet contains a field for the wait time in milliseconds.
|
||
This code takes the wait time and converts it into an
|
||
absolute filetime when the command packet is put on the
|
||
queue. The timeout triggers when the time is equal to or
|
||
greater than the command packet's filetime.
|
||
|
||
Author:
|
||
Billy J. Fuller 21-Feb-1998
|
||
|
||
Environment
|
||
User mode winnt
|
||
|
||
--*/
|
||
|
||
#include <ntreppch.h>
|
||
#pragma hdrstop
|
||
|
||
#include <frs.h>
|
||
|
||
//
|
||
// Struct for the Delayed Command Server
|
||
// Contains info about the queues and the threads
|
||
//
|
||
//
|
||
// The wait thread exits if nothing shows up in 5 minutes.
|
||
//
|
||
#define WAIT_EXIT_TIMEOUT (5 * 60 * 1000) // 5 minutes
|
||
|
||
//
|
||
// A command packet times out if the current time is within
|
||
// 1 second of the requested timeout value (avoids precision
|
||
// problems with the waitable timer).
|
||
//
|
||
#define WAIT_FUZZY_TIMEOUT (1 * 1000 * 1000 * 10)
|
||
|
||
//
|
||
// When creating the wait thread, retry 10 times with a one
|
||
// second sleep in between retries
|
||
//
|
||
#define WAIT_RETRY_CREATE_THREAD_COUNT (10) // retry 10 times
|
||
#define WAIT_RETRY_TIMEOUT (1 * 1000) // 1 second
|
||
|
||
//
|
||
// The thread is running (or not). Exit after 5 minutes of idleness.
|
||
// Recreate on demand.
|
||
//
|
||
DWORD WaitIsRunning;
|
||
|
||
//
|
||
// List of timeout commands
|
||
//
|
||
CRITICAL_SECTION WaitLock;
|
||
CRITICAL_SECTION WaitUnsubmitLock;
|
||
LIST_ENTRY WaitList;
|
||
|
||
//
|
||
// Waitable timer. The thread waits on the timer and the queue's rundown event.
|
||
//
|
||
HANDLE WaitableTimer;
|
||
|
||
//
|
||
// Current timeout trigger in WaitableTimer
|
||
//
|
||
LONGLONG WaitFileTime;
|
||
|
||
//
|
||
// Set when the wait list is rundown
|
||
//
|
||
HANDLE WaitRunDown;
|
||
BOOL WaitIsRunDown;
|
||
|
||
|
||
VOID
|
||
WaitStartThread(
|
||
VOID
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Start the wait thread if it isn't running. The timer has been
|
||
set by the caller. The caller holds the WaitLock.
|
||
|
||
Arguments:
|
||
None.
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "WaitStartThread:"
|
||
DWORD Retries;
|
||
DWORD MainWait(PVOID Arg);
|
||
|
||
//
|
||
// Caller holds WaitLock
|
||
//
|
||
|
||
//
|
||
// Thread is running; done
|
||
//
|
||
if (WaitIsRunning) {
|
||
return;
|
||
}
|
||
//
|
||
// Queue is rundown; don't start
|
||
//
|
||
if (WaitIsRunDown) {
|
||
DPRINT(4, "Don't start wait thread; queue is rundown.\n");
|
||
return;
|
||
}
|
||
//
|
||
// Queue is empty; don't start
|
||
//
|
||
if (IsListEmpty(&WaitList)) {
|
||
DPRINT(4, "Don't start wait thread; queue is empty.\n");
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Start the wait thread. Retry several times.
|
||
//
|
||
if (!WaitIsRunning) {
|
||
Retries = WAIT_RETRY_CREATE_THREAD_COUNT;
|
||
while (!WaitIsRunning && Retries--) {
|
||
WaitIsRunning = ThSupCreateThread(L"Wait", &WaitList, MainWait, ThSupExitWithTombstone);
|
||
if (!WaitIsRunning) {
|
||
DPRINT(0, "WARN: Wait thread could not be started; retry later.\n");
|
||
Sleep(1 * 1000);
|
||
}
|
||
}
|
||
}
|
||
//
|
||
// Can't start the wait thread. Something is very wrong. Shutdown.
|
||
//
|
||
if (!WaitIsRunning) {
|
||
FrsIsShuttingDown = TRUE;
|
||
SetEvent(ShutDownEvent);
|
||
return;
|
||
}
|
||
}
|
||
|
||
VOID
|
||
WaitReset(
|
||
IN BOOL ResetTimer
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Complete the command packets that have timed out. Reset the timer.
|
||
|
||
Caller holds WaitLock.
|
||
|
||
Arguments:
|
||
ResetTimer - reset timer always
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "WaitReset:"
|
||
PCOMMAND_PACKET Cmd;
|
||
PLIST_ENTRY Entry;
|
||
LONGLONG Now;
|
||
BOOL StartThread = FALSE;
|
||
|
||
//
|
||
// Entries are sorted by absolute timeout
|
||
//
|
||
if (IsListEmpty(&WaitList)) {
|
||
//
|
||
// Allow the thread to exit in 5 minutes if no work shows up
|
||
//
|
||
if (WaitIsRunning) {
|
||
FrsNowAsFileTime(&Now);
|
||
WaitFileTime = Now + ((LONGLONG)WAIT_EXIT_TIMEOUT * 1000 * 10);
|
||
ResetTimer = TRUE;
|
||
}
|
||
} else {
|
||
StartThread = TRUE;
|
||
Entry = GetListNext(&WaitList);
|
||
Cmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
|
||
//
|
||
// Reset timeout
|
||
//
|
||
if ((Cmd->WaitFileTime != WaitFileTime) || ResetTimer) {
|
||
WaitFileTime = Cmd->WaitFileTime;
|
||
ResetTimer = TRUE;
|
||
}
|
||
}
|
||
//
|
||
// Reset the timer
|
||
//
|
||
if (ResetTimer) {
|
||
DPRINT1(4, "Resetting timer to %08x %08x.\n", PRINTQUAD(WaitFileTime));
|
||
|
||
if (!SetWaitableTimer(WaitableTimer, (LARGE_INTEGER *)&WaitFileTime, 0, NULL, NULL, TRUE)) {
|
||
DPRINT_WS(0, "ERROR - Resetting timer;", GetLastError());
|
||
}
|
||
}
|
||
//
|
||
// Make sure the thread is running
|
||
//
|
||
if (StartThread && !WaitIsRunning) {
|
||
WaitStartThread();
|
||
}
|
||
}
|
||
|
||
|
||
VOID
|
||
WaitUnsubmit(
|
||
IN PCOMMAND_PACKET Cmd
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Pull the command packet off of the timeout queue and adjust
|
||
the timer. NOP if the command packet is not on the command queue.
|
||
|
||
Arguments:
|
||
Cmd - command packet to pull off the queue
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "WaitUnsubmit:"
|
||
BOOL Reset = FALSE;
|
||
|
||
//
|
||
// Defensive
|
||
//
|
||
if (Cmd == NULL) {
|
||
return;
|
||
}
|
||
|
||
DPRINT5(4, "UnSubmit cmd %08x (%08x) for timeout (%08x) in %d ms (%08x)\n",
|
||
Cmd->Command, Cmd, Cmd->TimeoutCommand, Cmd->Timeout, Cmd->WaitFlags);
|
||
|
||
EnterCriticalSection(&WaitLock);
|
||
EnterCriticalSection(&WaitUnsubmitLock);
|
||
|
||
//
|
||
// Entries are sorted by absolute timeout
|
||
//
|
||
if (CmdWaitFlagIs(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST)) {
|
||
RemoveEntryListB(&Cmd->ListEntry);
|
||
ClearCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
|
||
Reset = TRUE;
|
||
}
|
||
LeaveCriticalSection(&WaitUnsubmitLock);
|
||
//
|
||
// Reset the timer if the expiration time has changed
|
||
//
|
||
if (Reset) {
|
||
WaitReset(FALSE);
|
||
}
|
||
LeaveCriticalSection(&WaitLock);
|
||
}
|
||
|
||
|
||
VOID
|
||
WaitProcessCommand(
|
||
IN PCOMMAND_PACKET Cmd,
|
||
IN DWORD ErrorStatus
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Process the timed out command packet. The timeout values are
|
||
unaffected.
|
||
|
||
Arguments:
|
||
Cmd - command packet that timed out or errored out
|
||
ErrorStatus - ERROR_SUCCESS if timed out
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "WaitProcessCommand:"
|
||
|
||
DPRINT5(4, "Process cmd %08x (%08x) for timeout (%08x) in %d ms (%08x)\n",
|
||
Cmd->Command, Cmd, Cmd->TimeoutCommand, Cmd->Timeout, Cmd->WaitFlags);
|
||
|
||
switch (Cmd->TimeoutCommand) {
|
||
//
|
||
// Submit a command
|
||
//
|
||
case CMD_DELAYED_SUBMIT:
|
||
FrsSubmitCommand(Cmd, FALSE);
|
||
break;
|
||
|
||
//
|
||
// Run the command packet's completion routine
|
||
//
|
||
case CMD_DELAYED_COMPLETE:
|
||
FrsCompleteCommand(Cmd, ErrorStatus);
|
||
break;
|
||
|
||
//
|
||
// Unknown command
|
||
//
|
||
default:
|
||
DPRINT1(0, "ERROR - Wait: Unknown command 0x%x.\n", Cmd->TimeoutCommand);
|
||
FRS_ASSERT(!"invalid comm timeout command stuck on list");
|
||
FrsCompleteCommand(Cmd, ERROR_INVALID_FUNCTION);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
DWORD
|
||
WaitSubmit(
|
||
IN PCOMMAND_PACKET Cmd,
|
||
IN DWORD Timeout,
|
||
IN USHORT TimeoutCommand
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Insert the new command packet into the sorted, timeout list
|
||
|
||
Restart the thread if needed.
|
||
|
||
The Cmd may already be on the timeout list. If so, simply
|
||
adjust its timeout.
|
||
|
||
Arguments:
|
||
Cmd - Command packet to timeout
|
||
Timeout - Timeout in milliseconds from now
|
||
TimeoutCommand - Disposition at timeout
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "WaitSubmit:"
|
||
PLIST_ENTRY Entry;
|
||
LONGLONG Now;
|
||
PCOMMAND_PACKET OldCmd;
|
||
DWORD WStatus = ERROR_SUCCESS;
|
||
|
||
//
|
||
// Defensive
|
||
//
|
||
if (Cmd == NULL) {
|
||
return ERROR_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Setup the command packet with timeout and wait specific info
|
||
// We acquire the lock now just in case the command
|
||
// is already on the list.
|
||
//
|
||
EnterCriticalSection(&WaitLock);
|
||
|
||
Cmd->Timeout = Timeout;
|
||
Cmd->TimeoutCommand = TimeoutCommand;
|
||
FrsNowAsFileTime(&Now);
|
||
Cmd->WaitFileTime = Now + ((LONGLONG)Cmd->Timeout * 1000 * 10);
|
||
|
||
DPRINT5(4, "Submit cmd %08x (%08x) for timeout (%08x) in %d ms (%08x)\n",
|
||
Cmd->Command, Cmd, Cmd->TimeoutCommand, Cmd->Timeout, Cmd->WaitFlags);
|
||
|
||
//
|
||
// Remove from list
|
||
//
|
||
if (CmdWaitFlagIs(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST)) {
|
||
RemoveEntryListB(&Cmd->ListEntry);
|
||
ClearCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
|
||
}
|
||
|
||
//
|
||
// Is the queue rundown?
|
||
//
|
||
if (WaitIsRunDown) {
|
||
DPRINT2(4, "Can't insert cmd %08x (%08x); queue rundown\n",
|
||
Cmd->Command, Cmd);
|
||
WStatus = ERROR_ACCESS_DENIED;
|
||
goto CLEANUP;
|
||
}
|
||
|
||
//
|
||
// Insert into empty list
|
||
//
|
||
if (IsListEmpty(&WaitList)) {
|
||
InsertHeadList(&WaitList, &Cmd->ListEntry);
|
||
SetCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
|
||
goto CLEANUP;
|
||
}
|
||
|
||
//
|
||
// Insert at tail
|
||
//
|
||
Entry = GetListTail(&WaitList);
|
||
OldCmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
|
||
if (OldCmd->WaitFileTime <= Cmd->WaitFileTime) {
|
||
InsertTailList(&WaitList, &Cmd->ListEntry);
|
||
SetCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
|
||
goto CLEANUP;
|
||
}
|
||
//
|
||
// Insert into list
|
||
//
|
||
for (Entry = GetListHead(&WaitList);
|
||
Entry != &WaitList;
|
||
Entry = GetListNext(Entry)) {
|
||
|
||
OldCmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
|
||
if (Cmd->WaitFileTime <= OldCmd->WaitFileTime) {
|
||
InsertTailList(Entry, &Cmd->ListEntry);
|
||
SetCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
|
||
goto CLEANUP;
|
||
}
|
||
}
|
||
|
||
CLEANUP:
|
||
//
|
||
// Reset the timer if the expiration time has changed
|
||
//
|
||
if (WIN_SUCCESS(WStatus)) {
|
||
WaitReset(FALSE);
|
||
}
|
||
LeaveCriticalSection(&WaitLock);
|
||
|
||
return WStatus;
|
||
}
|
||
|
||
|
||
VOID
|
||
WaitTimeout(
|
||
VOID
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Expel the commands whose timeouts have passed.
|
||
|
||
Arguments:
|
||
None.
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "WaitTimeout:"
|
||
PLIST_ENTRY Entry;
|
||
PCOMMAND_PACKET Cmd;
|
||
LONGLONG Now;
|
||
|
||
//
|
||
// Expel expired commands
|
||
//
|
||
FrsNowAsFileTime(&Now);
|
||
EnterCriticalSection(&WaitLock);
|
||
while (!IsListEmpty(&WaitList)) {
|
||
Entry = GetListHead(&WaitList);
|
||
Cmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
|
||
//
|
||
// Hasn't timed out; stop
|
||
//
|
||
if ((Cmd->WaitFileTime - WAIT_FUZZY_TIMEOUT) > Now) {
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Timed out; process it. Be careful to synchronize with
|
||
// WaitUnsubmit.
|
||
//
|
||
RemoveEntryListB(Entry);
|
||
ClearCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
|
||
EnterCriticalSection(&WaitUnsubmitLock);
|
||
LeaveCriticalSection(&WaitLock);
|
||
WaitProcessCommand(Cmd, ERROR_SUCCESS);
|
||
LeaveCriticalSection(&WaitUnsubmitLock);
|
||
EnterCriticalSection(&WaitLock);
|
||
}
|
||
//
|
||
// Reset the timer (always)
|
||
//
|
||
WaitReset(TRUE);
|
||
LeaveCriticalSection(&WaitLock);
|
||
}
|
||
|
||
|
||
VOID
|
||
WaitRunDownList(
|
||
VOID
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Error off the commands in the timeout list
|
||
|
||
Arguments:
|
||
None.
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "WaitRunDownList:"
|
||
PLIST_ENTRY Entry;
|
||
PCOMMAND_PACKET Cmd;
|
||
|
||
//
|
||
// Rundown commands
|
||
//
|
||
EnterCriticalSection(&WaitLock);
|
||
while (!IsListEmpty(&WaitList)) {
|
||
Entry = GetListHead(&WaitList);
|
||
Cmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
|
||
|
||
RemoveEntryListB(Entry);
|
||
ClearCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
|
||
|
||
EnterCriticalSection(&WaitUnsubmitLock);
|
||
LeaveCriticalSection(&WaitLock);
|
||
|
||
WaitProcessCommand(Cmd, ERROR_ACCESS_DENIED);
|
||
|
||
LeaveCriticalSection(&WaitUnsubmitLock);
|
||
EnterCriticalSection(&WaitLock);
|
||
}
|
||
|
||
FrsNowAsFileTime(&WaitFileTime);
|
||
DPRINT1(4, "Resetting rundown timer to %08x %08x.\n", PRINTQUAD(WaitFileTime));
|
||
|
||
SetWaitableTimer(WaitableTimer, (LARGE_INTEGER *)&WaitFileTime, 0, NULL, NULL, TRUE);
|
||
WaitIsRunDown = TRUE;
|
||
LeaveCriticalSection(&WaitLock);
|
||
}
|
||
|
||
|
||
DWORD
|
||
MainWait(
|
||
PFRS_THREAD FrsThread
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Entry point for a thread serving the wait queue.
|
||
A timeout list is managed by a thread waiting on a
|
||
waitable timer.
|
||
|
||
The timer can be adjusted without context switching to the
|
||
thread that is waiting on the timer.
|
||
|
||
An entry can be pulled off the list. The timer is adjusted
|
||
if the entry was at the head of the queue.
|
||
|
||
The queue is sorted by timeout value. The timeout value is
|
||
an absolute filetime.
|
||
|
||
The list entry is a command packet. The generic command
|
||
packet contains a field for the wait time in milliseconds.
|
||
This code takes the wait time and converts it into an
|
||
absolute filetime when the command packet is put on the
|
||
queue. The timeout triggers when the time is equal to or
|
||
greater than the command packet's filetime.
|
||
|
||
Arguments:
|
||
Arg - thread
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "MainWait:"
|
||
HANDLE WaitArray[2];
|
||
|
||
//
|
||
// Thread is pointing at the correct queue
|
||
//
|
||
FRS_ASSERT(FrsThread->Data == &WaitList);
|
||
|
||
DPRINT(0, "Wait thread has started.\n");
|
||
|
||
again:
|
||
//
|
||
// Wait for work, an exit timeout, or the queue to be rundown
|
||
//
|
||
DPRINT(4, "Wait thread is waiting.\n");
|
||
WaitArray[0] = WaitRunDown;
|
||
WaitArray[1] = WaitableTimer;
|
||
|
||
WaitForMultipleObjectsEx(2, WaitArray, FALSE, INFINITE, TRUE);
|
||
|
||
DPRINT(4, "Wait thread is running.\n");
|
||
//
|
||
// Nothing to do; exit
|
||
//
|
||
EnterCriticalSection(&WaitLock);
|
||
if (IsListEmpty(&WaitList)) {
|
||
WaitIsRunning = FALSE;
|
||
LeaveCriticalSection(&WaitLock);
|
||
|
||
DPRINT(0, "Wait thread is exiting.\n");
|
||
ThSupSubmitThreadExitCleanup(FrsThread);
|
||
ExitThread(ERROR_SUCCESS);
|
||
}
|
||
LeaveCriticalSection(&WaitLock);
|
||
|
||
//
|
||
// Check for timed out commands
|
||
//
|
||
WaitTimeout();
|
||
|
||
//
|
||
// Continue forever
|
||
//
|
||
goto again;
|
||
return ERROR_SUCCESS;
|
||
}
|
||
|
||
|
||
VOID
|
||
WaitInitialize(
|
||
VOID
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Initialize the wait subsystem. The thread is kicked off
|
||
on demand.
|
||
|
||
Arguments:
|
||
None.
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "WaitInitialize:"
|
||
//
|
||
// Timeout list
|
||
//
|
||
InitializeListHead(&WaitList);
|
||
InitializeCriticalSection(&WaitLock);
|
||
InitializeCriticalSection(&WaitUnsubmitLock);
|
||
|
||
//
|
||
// Rundown event for list
|
||
//
|
||
WaitRunDown = FrsCreateEvent(TRUE, FALSE);
|
||
|
||
//
|
||
// Timer
|
||
//
|
||
FrsNowAsFileTime(&WaitFileTime);
|
||
WaitableTimer = FrsCreateWaitableTimer(TRUE);
|
||
DPRINT1(4, "Setting initial timer to %08x %08x.\n", PRINTQUAD(WaitFileTime));
|
||
|
||
if (!SetWaitableTimer(WaitableTimer, (LARGE_INTEGER *)&WaitFileTime, 0, NULL, NULL, TRUE)) {
|
||
DPRINT_WS(0, "ERROR - Resetting timer;", GetLastError());
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
VOID
|
||
ShutDownWait(
|
||
VOID
|
||
)
|
||
/*++
|
||
Routine Description:
|
||
Shutdown the wait subsystem
|
||
|
||
Arguments:
|
||
None.
|
||
|
||
Return Value:
|
||
None.
|
||
--*/
|
||
{
|
||
#undef DEBSUB
|
||
#define DEBSUB "ShutDownWait:"
|
||
WaitRunDownList();
|
||
}
|