mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
705 lines
16 KiB
705 lines
16 KiB
/*++
|
|
|
|
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();
|
|
}
|