windows-nt/Source/XPSP1/NT/shell/ext/ftp/ftpgto.cpp
2020-09-26 16:20:57 +08:00

288 lines
9.9 KiB
C++

/*****************************************************************************
*
* ftpgto.cpp - Global timeouts
*
* Global timeouts are managed by a separate worker thread, whose job
* it is to hang around and perform delayed actions on request.
*
* All requests are for FTP_SESSION_TIME_OUT milliseconds. If nothing happens
* for an additional FTP_SESSION_TIME_OUT milliseconds, the worker thread is
* terminated.
*
*****************************************************************************/
#include "priv.h"
#include "util.h"
#define MS_PER_SECOND 1000
#define SECONDS_PER_MINUTE 60
#define FTP_SESSION_TIME_OUT (10 * SECONDS_PER_MINUTE * MS_PER_SECOND) // Survive 10 minutes in cache
BOOL g_fBackgroundThreadStarted; // Has the background thread started?
HANDLE g_hthWorker; // Background worker thread
HANDLE g_hFlushDelayedActionsEvent = NULL; // Do we want to flush the delayed actions?
/*****************************************************************************
*
* Global Timeout Info
*
* We must allocate separate information to track timeouts. Stashing
* the information into a buffer provided by the caller opens race
* conditions, if the caller frees the memory before we are ready.
*
* dwTrigger is 0 if the timeout is being dispatched. This avoids
* race conditions where one thread triggers a timeout manually
* while it is in progress.
*
*****************************************************************************/
struct GLOBALTIMEOUTINFO g_gti = { // Anchor of global timeout info list
&g_gti,
&g_gti,
0, 0, 0
};
/*****************************************************************************
* TriggerDelayedAction
*
* Unlink the node and dispatch the timeout procedure.
*****************************************************************************/
void TriggerDelayedAction(LPGLOBALTIMEOUTINFO * phgti)
{
LPGLOBALTIMEOUTINFO hgti = *phgti;
*phgti = NULL;
if (hgti)
{
ENTERCRITICAL;
if (hgti->dwTrigger)
{
// Unlink the node
hgti->hgtiPrev->hgtiNext = hgti->hgtiNext;
hgti->hgtiNext->hgtiPrev = hgti->hgtiPrev;
hgti->dwTrigger = 0;
// Do the callback
if (hgti->pfn)
hgti->pfn(hgti->pvRef);
LEAVECRITICAL;
TraceMsg(TF_BKGD_THREAD, "TriggerDelayedAction(%#08lx) Freeing=%#08lx", phgti, hgti);
DEBUG_CODE(memset(hgti, 0xFE, (UINT) LocalSize((HLOCAL)hgti)));
LocalFree((LPVOID) hgti);
}
else
{
LEAVECRITICAL;
}
}
}
/*****************************************************************************
* FtpDelayedActionWorkerThread
*
* This is the procedure that runs on the worker thread. It waits
* for something to do, and if enough time elapses with nothing
* to do, it terminates.
*
* Be extremely mindful of race conditions. They are oft subtle
* and quick to anger.
*****************************************************************************/
DWORD FtpDelayedActionWorkerThread(LPVOID pv)
{
// Tell the caller we started so they can continue.
g_fBackgroundThreadStarted = TRUE;
for (;;)
{
DWORD msWait;
// Determine how long we need to wait. The critical section
// is necessary to ensure we don't collide with SetDelayedAction.
ENTERCRITICAL;
if (g_gti.hgtiNext == &g_gti)
{
// Queue is empty
msWait = FTP_SESSION_TIME_OUT;
}
else
{
msWait = g_gti.hgtiNext->dwTrigger - GetTickCount();
}
LEAVECRITICAL;
// If a new delayed action gets added, no matter, because
// we will wake up from the sleep before the delayed action
// is due.
ASSERTNONCRITICAL;
if ((int)msWait > 0)
{
TraceMsg(TF_BKGD_THREAD, "FtpDelayedActionWorkerThread: Sleep(%d)", msWait);
WaitForMultipleObjects(1, &g_hFlushDelayedActionsEvent, FALSE, msWait);
TraceMsg(TF_BKGD_THREAD, "FtpDelayedActionWorkerThread: Sleep finished");
}
ENTERCRITICALNOASSERT;
if ((g_gti.hgtiNext != &g_gti) && g_gti.hgtiNext && (g_gti.hgtiNext->phgtiOwner))
{
// Queue has work
// RaymondC made a comment here that there is a race condition but I have never
// been able to see it. He made this comment years ago when he owned the code
// and I've re-writen parts and ensured it's thread safe. We never found any
// stress problems so this is just a reminder that this code is very thread
// sensitive.
LEAVECRITICAL;
TraceMsg(TF_BKGD_THREAD, "FtpDelayedActionWorkerThread: Dispatching");
TriggerDelayedAction(g_gti.hgtiNext->phgtiOwner);
}
else
{
CloseHandle(InterlockedExchangePointer(&g_hthWorker, NULL));
CloseHandle(InterlockedExchangePointer(&g_hFlushDelayedActionsEvent, NULL));
LEAVECRITICALNOASSERT;
TraceMsg(TF_BKGD_THREAD, "FtpDelayedActionWorkerThread: ExitThread");
ExitThread(0);
}
}
AssertMsg(0, TEXT("FtpDelayedActionWorkerThread() We should never get here or we are exiting the for loop incorrectly."));
return 0;
}
/*****************************************************************************
* SetDelayedAction
*
* If there is a previous action, it is triggered. (Not cancelled.)
*
* In principle, we could've allocated into a private pointer, then
* stuffed the pointer in at the last minute, avoiding the need to
* take the critical section so aggressively. But that would tend
* to open race conditions in the callers. So? I should
* fix the bugs instead of hacking around them like this.
*****************************************************************************/
STDMETHODIMP SetDelayedAction(DELAYEDACTIONPROC pfn, LPVOID pvRef, LPGLOBALTIMEOUTINFO * phgti)
{
TriggerDelayedAction(phgti);
ENTERCRITICAL;
if (!g_hthWorker)
{
DWORD dwThid;
g_hFlushDelayedActionsEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (g_hFlushDelayedActionsEvent)
{
g_fBackgroundThreadStarted = FALSE;
g_hthWorker = CreateThread(0, 0, FtpDelayedActionWorkerThread, 0, 0, &dwThid);
if (g_hthWorker)
{
// We need to wait until the thread starts up
// before we return. Otherwise, we may return to the
// caller and they may free our COM object
// which will unload our DLL. The thread won't
// start if we are in PROCESS_DLL_DETACH and we
// spin waiting for them to start and stop.
TraceMsg(TF_BKGD_THREAD, "SetDelayedAction: Thread created, waiting for it to start.");
while (FALSE == g_fBackgroundThreadStarted)
Sleep(0);
TraceMsg(TF_BKGD_THREAD, "SetDelayedAction: Thread started.");
}
else
{
CloseHandle(g_hFlushDelayedActionsEvent);
g_hFlushDelayedActionsEvent = NULL;
}
}
}
if (g_hthWorker && EVAL(*phgti = (LPGLOBALTIMEOUTINFO) LocalAlloc(LPTR, sizeof(GLOBALTIMEOUTINFO))))
{
LPGLOBALTIMEOUTINFO hgti = *phgti;
// Insert the node at the end (i.e., before the head)
hgti->hgtiPrev = g_gti.hgtiPrev;
g_gti.hgtiPrev->hgtiNext = hgti;
g_gti.hgtiPrev = hgti;
hgti->hgtiNext = &g_gti;
// The "|1" ensures that dwTrigger is not zero
hgti->dwTrigger = (GetTickCount() + FTP_SESSION_TIME_OUT) | 1;
hgti->pfn = pfn;
hgti->pvRef = pvRef;
hgti->phgtiOwner = phgti;
// Note that there is no need to signal the worker thread that
// there is new work to do, because he will always wake up on
// his own before the requisite time has elapsed.
//
// This optimization relies on the fact that the worker thread
// idle time is less than or equal to our delayed action time.
LEAVECRITICAL;
}
else
{
// Unable to create worker thread or alloc memory
LEAVECRITICAL;
}
return S_OK;
}
HRESULT PurgeDelayedActions(void)
{
HRESULT hr = E_FAIL;
if (g_hFlushDelayedActionsEvent)
{
LPGLOBALTIMEOUTINFO hgti = g_gti.hgtiNext;
// We need to set all the times to zero so all waiting
// items will not be delayed.
ENTERCRITICAL;
while (hgti != &g_gti)
{
hgti->dwTrigger = (GetTickCount() - 3); // Don't Delay...
hgti = hgti->hgtiNext; // Next...
}
LEAVECRITICAL;
if (SetEvent(g_hFlushDelayedActionsEvent))
{
// We can't be in a critical section or our background
// thread can't come alive.
ASSERTNONCRITICAL;
TraceMsg(TF_BKGD_THREAD, "PurgeDelayedActions: Waiting for thread to stop.");
// Now just wait for the thread to finish. Someone may kill
// the thread so let's make sure we don't keep sleeping
// if the thread died.
while (g_hthWorker && (WAIT_TIMEOUT == WaitForSingleObject(g_hthWorker, 0)))
Sleep(0);
TraceMsg(TF_BKGD_THREAD, "PurgeDelayedActions: Thread stopped.");
// Sleep 0.1 seconds in order to give enough time for caller
// to call CloseHandle(), LEAVECRITICAL, ExitThread(0).
// I would much prefer to call WaitForSingleObject() on
// the thread handle but I can't do that in PROCESS_DLL_DETACH.
Sleep(100);
hr = S_OK;
}
}
return hr;
}
BOOL AreOutstandingDelayedActions(void)
{
return (g_gti.hgtiNext != &g_gti);
}