windows-nt/Source/XPSP1/NT/ds/netapi/svcimgs/ntrepl/util/waittime.c

706 lines
16 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
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();
}