Source code of Windows XP (NT5)
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.

287 lines
9.9 KiB

  1. /*****************************************************************************
  2. *
  3. * ftpgto.cpp - Global timeouts
  4. *
  5. * Global timeouts are managed by a separate worker thread, whose job
  6. * it is to hang around and perform delayed actions on request.
  7. *
  8. * All requests are for FTP_SESSION_TIME_OUT milliseconds. If nothing happens
  9. * for an additional FTP_SESSION_TIME_OUT milliseconds, the worker thread is
  10. * terminated.
  11. *
  12. *****************************************************************************/
  13. #include "priv.h"
  14. #include "util.h"
  15. #define MS_PER_SECOND 1000
  16. #define SECONDS_PER_MINUTE 60
  17. #define FTP_SESSION_TIME_OUT (10 * SECONDS_PER_MINUTE * MS_PER_SECOND) // Survive 10 minutes in cache
  18. BOOL g_fBackgroundThreadStarted; // Has the background thread started?
  19. HANDLE g_hthWorker; // Background worker thread
  20. HANDLE g_hFlushDelayedActionsEvent = NULL; // Do we want to flush the delayed actions?
  21. /*****************************************************************************
  22. *
  23. * Global Timeout Info
  24. *
  25. * We must allocate separate information to track timeouts. Stashing
  26. * the information into a buffer provided by the caller opens race
  27. * conditions, if the caller frees the memory before we are ready.
  28. *
  29. * dwTrigger is 0 if the timeout is being dispatched. This avoids
  30. * race conditions where one thread triggers a timeout manually
  31. * while it is in progress.
  32. *
  33. *****************************************************************************/
  34. struct GLOBALTIMEOUTINFO g_gti = { // Anchor of global timeout info list
  35. &g_gti,
  36. &g_gti,
  37. 0, 0, 0
  38. };
  39. /*****************************************************************************
  40. * TriggerDelayedAction
  41. *
  42. * Unlink the node and dispatch the timeout procedure.
  43. *****************************************************************************/
  44. void TriggerDelayedAction(LPGLOBALTIMEOUTINFO * phgti)
  45. {
  46. LPGLOBALTIMEOUTINFO hgti = *phgti;
  47. *phgti = NULL;
  48. if (hgti)
  49. {
  50. ENTERCRITICAL;
  51. if (hgti->dwTrigger)
  52. {
  53. // Unlink the node
  54. hgti->hgtiPrev->hgtiNext = hgti->hgtiNext;
  55. hgti->hgtiNext->hgtiPrev = hgti->hgtiPrev;
  56. hgti->dwTrigger = 0;
  57. // Do the callback
  58. if (hgti->pfn)
  59. hgti->pfn(hgti->pvRef);
  60. LEAVECRITICAL;
  61. TraceMsg(TF_BKGD_THREAD, "TriggerDelayedAction(%#08lx) Freeing=%#08lx", phgti, hgti);
  62. DEBUG_CODE(memset(hgti, 0xFE, (UINT) LocalSize((HLOCAL)hgti)));
  63. LocalFree((LPVOID) hgti);
  64. }
  65. else
  66. {
  67. LEAVECRITICAL;
  68. }
  69. }
  70. }
  71. /*****************************************************************************
  72. * FtpDelayedActionWorkerThread
  73. *
  74. * This is the procedure that runs on the worker thread. It waits
  75. * for something to do, and if enough time elapses with nothing
  76. * to do, it terminates.
  77. *
  78. * Be extremely mindful of race conditions. They are oft subtle
  79. * and quick to anger.
  80. *****************************************************************************/
  81. DWORD FtpDelayedActionWorkerThread(LPVOID pv)
  82. {
  83. // Tell the caller we started so they can continue.
  84. g_fBackgroundThreadStarted = TRUE;
  85. for (;;)
  86. {
  87. DWORD msWait;
  88. // Determine how long we need to wait. The critical section
  89. // is necessary to ensure we don't collide with SetDelayedAction.
  90. ENTERCRITICAL;
  91. if (g_gti.hgtiNext == &g_gti)
  92. {
  93. // Queue is empty
  94. msWait = FTP_SESSION_TIME_OUT;
  95. }
  96. else
  97. {
  98. msWait = g_gti.hgtiNext->dwTrigger - GetTickCount();
  99. }
  100. LEAVECRITICAL;
  101. // If a new delayed action gets added, no matter, because
  102. // we will wake up from the sleep before the delayed action
  103. // is due.
  104. ASSERTNONCRITICAL;
  105. if ((int)msWait > 0)
  106. {
  107. TraceMsg(TF_BKGD_THREAD, "FtpDelayedActionWorkerThread: Sleep(%d)", msWait);
  108. WaitForMultipleObjects(1, &g_hFlushDelayedActionsEvent, FALSE, msWait);
  109. TraceMsg(TF_BKGD_THREAD, "FtpDelayedActionWorkerThread: Sleep finished");
  110. }
  111. ENTERCRITICALNOASSERT;
  112. if ((g_gti.hgtiNext != &g_gti) && g_gti.hgtiNext && (g_gti.hgtiNext->phgtiOwner))
  113. {
  114. // Queue has work
  115. // RaymondC made a comment here that there is a race condition but I have never
  116. // been able to see it. He made this comment years ago when he owned the code
  117. // and I've re-writen parts and ensured it's thread safe. We never found any
  118. // stress problems so this is just a reminder that this code is very thread
  119. // sensitive.
  120. LEAVECRITICAL;
  121. TraceMsg(TF_BKGD_THREAD, "FtpDelayedActionWorkerThread: Dispatching");
  122. TriggerDelayedAction(g_gti.hgtiNext->phgtiOwner);
  123. }
  124. else
  125. {
  126. CloseHandle(InterlockedExchangePointer(&g_hthWorker, NULL));
  127. CloseHandle(InterlockedExchangePointer(&g_hFlushDelayedActionsEvent, NULL));
  128. LEAVECRITICALNOASSERT;
  129. TraceMsg(TF_BKGD_THREAD, "FtpDelayedActionWorkerThread: ExitThread");
  130. ExitThread(0);
  131. }
  132. }
  133. AssertMsg(0, TEXT("FtpDelayedActionWorkerThread() We should never get here or we are exiting the for loop incorrectly."));
  134. return 0;
  135. }
  136. /*****************************************************************************
  137. * SetDelayedAction
  138. *
  139. * If there is a previous action, it is triggered. (Not cancelled.)
  140. *
  141. * In principle, we could've allocated into a private pointer, then
  142. * stuffed the pointer in at the last minute, avoiding the need to
  143. * take the critical section so aggressively. But that would tend
  144. * to open race conditions in the callers. So? I should
  145. * fix the bugs instead of hacking around them like this.
  146. *****************************************************************************/
  147. STDMETHODIMP SetDelayedAction(DELAYEDACTIONPROC pfn, LPVOID pvRef, LPGLOBALTIMEOUTINFO * phgti)
  148. {
  149. TriggerDelayedAction(phgti);
  150. ENTERCRITICAL;
  151. if (!g_hthWorker)
  152. {
  153. DWORD dwThid;
  154. g_hFlushDelayedActionsEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  155. if (g_hFlushDelayedActionsEvent)
  156. {
  157. g_fBackgroundThreadStarted = FALSE;
  158. g_hthWorker = CreateThread(0, 0, FtpDelayedActionWorkerThread, 0, 0, &dwThid);
  159. if (g_hthWorker)
  160. {
  161. // We need to wait until the thread starts up
  162. // before we return. Otherwise, we may return to the
  163. // caller and they may free our COM object
  164. // which will unload our DLL. The thread won't
  165. // start if we are in PROCESS_DLL_DETACH and we
  166. // spin waiting for them to start and stop.
  167. TraceMsg(TF_BKGD_THREAD, "SetDelayedAction: Thread created, waiting for it to start.");
  168. while (FALSE == g_fBackgroundThreadStarted)
  169. Sleep(0);
  170. TraceMsg(TF_BKGD_THREAD, "SetDelayedAction: Thread started.");
  171. }
  172. else
  173. {
  174. CloseHandle(g_hFlushDelayedActionsEvent);
  175. g_hFlushDelayedActionsEvent = NULL;
  176. }
  177. }
  178. }
  179. if (g_hthWorker && EVAL(*phgti = (LPGLOBALTIMEOUTINFO) LocalAlloc(LPTR, sizeof(GLOBALTIMEOUTINFO))))
  180. {
  181. LPGLOBALTIMEOUTINFO hgti = *phgti;
  182. // Insert the node at the end (i.e., before the head)
  183. hgti->hgtiPrev = g_gti.hgtiPrev;
  184. g_gti.hgtiPrev->hgtiNext = hgti;
  185. g_gti.hgtiPrev = hgti;
  186. hgti->hgtiNext = &g_gti;
  187. // The "|1" ensures that dwTrigger is not zero
  188. hgti->dwTrigger = (GetTickCount() + FTP_SESSION_TIME_OUT) | 1;
  189. hgti->pfn = pfn;
  190. hgti->pvRef = pvRef;
  191. hgti->phgtiOwner = phgti;
  192. // Note that there is no need to signal the worker thread that
  193. // there is new work to do, because he will always wake up on
  194. // his own before the requisite time has elapsed.
  195. //
  196. // This optimization relies on the fact that the worker thread
  197. // idle time is less than or equal to our delayed action time.
  198. LEAVECRITICAL;
  199. }
  200. else
  201. {
  202. // Unable to create worker thread or alloc memory
  203. LEAVECRITICAL;
  204. }
  205. return S_OK;
  206. }
  207. HRESULT PurgeDelayedActions(void)
  208. {
  209. HRESULT hr = E_FAIL;
  210. if (g_hFlushDelayedActionsEvent)
  211. {
  212. LPGLOBALTIMEOUTINFO hgti = g_gti.hgtiNext;
  213. // We need to set all the times to zero so all waiting
  214. // items will not be delayed.
  215. ENTERCRITICAL;
  216. while (hgti != &g_gti)
  217. {
  218. hgti->dwTrigger = (GetTickCount() - 3); // Don't Delay...
  219. hgti = hgti->hgtiNext; // Next...
  220. }
  221. LEAVECRITICAL;
  222. if (SetEvent(g_hFlushDelayedActionsEvent))
  223. {
  224. // We can't be in a critical section or our background
  225. // thread can't come alive.
  226. ASSERTNONCRITICAL;
  227. TraceMsg(TF_BKGD_THREAD, "PurgeDelayedActions: Waiting for thread to stop.");
  228. // Now just wait for the thread to finish. Someone may kill
  229. // the thread so let's make sure we don't keep sleeping
  230. // if the thread died.
  231. while (g_hthWorker && (WAIT_TIMEOUT == WaitForSingleObject(g_hthWorker, 0)))
  232. Sleep(0);
  233. TraceMsg(TF_BKGD_THREAD, "PurgeDelayedActions: Thread stopped.");
  234. // Sleep 0.1 seconds in order to give enough time for caller
  235. // to call CloseHandle(), LEAVECRITICAL, ExitThread(0).
  236. // I would much prefer to call WaitForSingleObject() on
  237. // the thread handle but I can't do that in PROCESS_DLL_DETACH.
  238. Sleep(100);
  239. hr = S_OK;
  240. }
  241. }
  242. return hr;
  243. }
  244. BOOL AreOutstandingDelayedActions(void)
  245. {
  246. return (g_gti.hgtiNext != &g_gti);
  247. }