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