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.

705 lines
16 KiB

  1. /*++
  2. Copyright (c) 1998 Microsoft Corporation
  3. Module Name:
  4. waittime.c
  5. Abstract:
  6. A timeout list is managed by a thread waiting on a
  7. waitable timer.
  8. The timer can be adjusted without context switching to the
  9. thread that is waiting on the timer.
  10. An entry can be pulled off the list. The timer is adjusted
  11. if the entry was at the head of the queue.
  12. The queue is sorted by timeout value. The timeout value is
  13. an absolute filetime.
  14. The list entry is a command packet. The generic command
  15. packet contains a field for the wait time in milliseconds.
  16. This code takes the wait time and converts it into an
  17. absolute filetime when the command packet is put on the
  18. queue. The timeout triggers when the time is equal to or
  19. greater than the command packet's filetime.
  20. Author:
  21. Billy J. Fuller 21-Feb-1998
  22. Environment
  23. User mode winnt
  24. --*/
  25. #include <ntreppch.h>
  26. #pragma hdrstop
  27. #include <frs.h>
  28. //
  29. // Struct for the Delayed Command Server
  30. // Contains info about the queues and the threads
  31. //
  32. //
  33. // The wait thread exits if nothing shows up in 5 minutes.
  34. //
  35. #define WAIT_EXIT_TIMEOUT (5 * 60 * 1000) // 5 minutes
  36. //
  37. // A command packet times out if the current time is within
  38. // 1 second of the requested timeout value (avoids precision
  39. // problems with the waitable timer).
  40. //
  41. #define WAIT_FUZZY_TIMEOUT (1 * 1000 * 1000 * 10)
  42. //
  43. // When creating the wait thread, retry 10 times with a one
  44. // second sleep in between retries
  45. //
  46. #define WAIT_RETRY_CREATE_THREAD_COUNT (10) // retry 10 times
  47. #define WAIT_RETRY_TIMEOUT (1 * 1000) // 1 second
  48. //
  49. // The thread is running (or not). Exit after 5 minutes of idleness.
  50. // Recreate on demand.
  51. //
  52. DWORD WaitIsRunning;
  53. //
  54. // List of timeout commands
  55. //
  56. CRITICAL_SECTION WaitLock;
  57. CRITICAL_SECTION WaitUnsubmitLock;
  58. LIST_ENTRY WaitList;
  59. //
  60. // Waitable timer. The thread waits on the timer and the queue's rundown event.
  61. //
  62. HANDLE WaitableTimer;
  63. //
  64. // Current timeout trigger in WaitableTimer
  65. //
  66. LONGLONG WaitFileTime;
  67. //
  68. // Set when the wait list is rundown
  69. //
  70. HANDLE WaitRunDown;
  71. BOOL WaitIsRunDown;
  72. VOID
  73. WaitStartThread(
  74. VOID
  75. )
  76. /*++
  77. Routine Description:
  78. Start the wait thread if it isn't running. The timer has been
  79. set by the caller. The caller holds the WaitLock.
  80. Arguments:
  81. None.
  82. Return Value:
  83. None.
  84. --*/
  85. {
  86. #undef DEBSUB
  87. #define DEBSUB "WaitStartThread:"
  88. DWORD Retries;
  89. DWORD MainWait(PVOID Arg);
  90. //
  91. // Caller holds WaitLock
  92. //
  93. //
  94. // Thread is running; done
  95. //
  96. if (WaitIsRunning) {
  97. return;
  98. }
  99. //
  100. // Queue is rundown; don't start
  101. //
  102. if (WaitIsRunDown) {
  103. DPRINT(4, "Don't start wait thread; queue is rundown.\n");
  104. return;
  105. }
  106. //
  107. // Queue is empty; don't start
  108. //
  109. if (IsListEmpty(&WaitList)) {
  110. DPRINT(4, "Don't start wait thread; queue is empty.\n");
  111. return;
  112. }
  113. //
  114. // Start the wait thread. Retry several times.
  115. //
  116. if (!WaitIsRunning) {
  117. Retries = WAIT_RETRY_CREATE_THREAD_COUNT;
  118. while (!WaitIsRunning && Retries--) {
  119. WaitIsRunning = ThSupCreateThread(L"Wait", &WaitList, MainWait, ThSupExitWithTombstone);
  120. if (!WaitIsRunning) {
  121. DPRINT(0, "WARN: Wait thread could not be started; retry later.\n");
  122. Sleep(1 * 1000);
  123. }
  124. }
  125. }
  126. //
  127. // Can't start the wait thread. Something is very wrong. Shutdown.
  128. //
  129. if (!WaitIsRunning) {
  130. FrsIsShuttingDown = TRUE;
  131. SetEvent(ShutDownEvent);
  132. return;
  133. }
  134. }
  135. VOID
  136. WaitReset(
  137. IN BOOL ResetTimer
  138. )
  139. /*++
  140. Routine Description:
  141. Complete the command packets that have timed out. Reset the timer.
  142. Caller holds WaitLock.
  143. Arguments:
  144. ResetTimer - reset timer always
  145. Return Value:
  146. None.
  147. --*/
  148. {
  149. #undef DEBSUB
  150. #define DEBSUB "WaitReset:"
  151. PCOMMAND_PACKET Cmd;
  152. PLIST_ENTRY Entry;
  153. LONGLONG Now;
  154. BOOL StartThread = FALSE;
  155. //
  156. // Entries are sorted by absolute timeout
  157. //
  158. if (IsListEmpty(&WaitList)) {
  159. //
  160. // Allow the thread to exit in 5 minutes if no work shows up
  161. //
  162. if (WaitIsRunning) {
  163. FrsNowAsFileTime(&Now);
  164. WaitFileTime = Now + ((LONGLONG)WAIT_EXIT_TIMEOUT * 1000 * 10);
  165. ResetTimer = TRUE;
  166. }
  167. } else {
  168. StartThread = TRUE;
  169. Entry = GetListNext(&WaitList);
  170. Cmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
  171. //
  172. // Reset timeout
  173. //
  174. if ((Cmd->WaitFileTime != WaitFileTime) || ResetTimer) {
  175. WaitFileTime = Cmd->WaitFileTime;
  176. ResetTimer = TRUE;
  177. }
  178. }
  179. //
  180. // Reset the timer
  181. //
  182. if (ResetTimer) {
  183. DPRINT1(4, "Resetting timer to %08x %08x.\n", PRINTQUAD(WaitFileTime));
  184. if (!SetWaitableTimer(WaitableTimer, (LARGE_INTEGER *)&WaitFileTime, 0, NULL, NULL, TRUE)) {
  185. DPRINT_WS(0, "ERROR - Resetting timer;", GetLastError());
  186. }
  187. }
  188. //
  189. // Make sure the thread is running
  190. //
  191. if (StartThread && !WaitIsRunning) {
  192. WaitStartThread();
  193. }
  194. }
  195. VOID
  196. WaitUnsubmit(
  197. IN PCOMMAND_PACKET Cmd
  198. )
  199. /*++
  200. Routine Description:
  201. Pull the command packet off of the timeout queue and adjust
  202. the timer. NOP if the command packet is not on the command queue.
  203. Arguments:
  204. Cmd - command packet to pull off the queue
  205. Return Value:
  206. None.
  207. --*/
  208. {
  209. #undef DEBSUB
  210. #define DEBSUB "WaitUnsubmit:"
  211. BOOL Reset = FALSE;
  212. //
  213. // Defensive
  214. //
  215. if (Cmd == NULL) {
  216. return;
  217. }
  218. DPRINT5(4, "UnSubmit cmd %08x (%08x) for timeout (%08x) in %d ms (%08x)\n",
  219. Cmd->Command, Cmd, Cmd->TimeoutCommand, Cmd->Timeout, Cmd->WaitFlags);
  220. EnterCriticalSection(&WaitLock);
  221. EnterCriticalSection(&WaitUnsubmitLock);
  222. //
  223. // Entries are sorted by absolute timeout
  224. //
  225. if (CmdWaitFlagIs(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST)) {
  226. RemoveEntryListB(&Cmd->ListEntry);
  227. ClearCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
  228. Reset = TRUE;
  229. }
  230. LeaveCriticalSection(&WaitUnsubmitLock);
  231. //
  232. // Reset the timer if the expiration time has changed
  233. //
  234. if (Reset) {
  235. WaitReset(FALSE);
  236. }
  237. LeaveCriticalSection(&WaitLock);
  238. }
  239. VOID
  240. WaitProcessCommand(
  241. IN PCOMMAND_PACKET Cmd,
  242. IN DWORD ErrorStatus
  243. )
  244. /*++
  245. Routine Description:
  246. Process the timed out command packet. The timeout values are
  247. unaffected.
  248. Arguments:
  249. Cmd - command packet that timed out or errored out
  250. ErrorStatus - ERROR_SUCCESS if timed out
  251. Return Value:
  252. None.
  253. --*/
  254. {
  255. #undef DEBSUB
  256. #define DEBSUB "WaitProcessCommand:"
  257. DPRINT5(4, "Process cmd %08x (%08x) for timeout (%08x) in %d ms (%08x)\n",
  258. Cmd->Command, Cmd, Cmd->TimeoutCommand, Cmd->Timeout, Cmd->WaitFlags);
  259. switch (Cmd->TimeoutCommand) {
  260. //
  261. // Submit a command
  262. //
  263. case CMD_DELAYED_SUBMIT:
  264. FrsSubmitCommand(Cmd, FALSE);
  265. break;
  266. //
  267. // Run the command packet's completion routine
  268. //
  269. case CMD_DELAYED_COMPLETE:
  270. FrsCompleteCommand(Cmd, ErrorStatus);
  271. break;
  272. //
  273. // Unknown command
  274. //
  275. default:
  276. DPRINT1(0, "ERROR - Wait: Unknown command 0x%x.\n", Cmd->TimeoutCommand);
  277. FRS_ASSERT(!"invalid comm timeout command stuck on list");
  278. FrsCompleteCommand(Cmd, ERROR_INVALID_FUNCTION);
  279. break;
  280. }
  281. }
  282. DWORD
  283. WaitSubmit(
  284. IN PCOMMAND_PACKET Cmd,
  285. IN DWORD Timeout,
  286. IN USHORT TimeoutCommand
  287. )
  288. /*++
  289. Routine Description:
  290. Insert the new command packet into the sorted, timeout list
  291. Restart the thread if needed.
  292. The Cmd may already be on the timeout list. If so, simply
  293. adjust its timeout.
  294. Arguments:
  295. Cmd - Command packet to timeout
  296. Timeout - Timeout in milliseconds from now
  297. TimeoutCommand - Disposition at timeout
  298. Return Value:
  299. None.
  300. --*/
  301. {
  302. #undef DEBSUB
  303. #define DEBSUB "WaitSubmit:"
  304. PLIST_ENTRY Entry;
  305. LONGLONG Now;
  306. PCOMMAND_PACKET OldCmd;
  307. DWORD WStatus = ERROR_SUCCESS;
  308. //
  309. // Defensive
  310. //
  311. if (Cmd == NULL) {
  312. return ERROR_SUCCESS;
  313. }
  314. //
  315. // Setup the command packet with timeout and wait specific info
  316. // We acquire the lock now just in case the command
  317. // is already on the list.
  318. //
  319. EnterCriticalSection(&WaitLock);
  320. Cmd->Timeout = Timeout;
  321. Cmd->TimeoutCommand = TimeoutCommand;
  322. FrsNowAsFileTime(&Now);
  323. Cmd->WaitFileTime = Now + ((LONGLONG)Cmd->Timeout * 1000 * 10);
  324. DPRINT5(4, "Submit cmd %08x (%08x) for timeout (%08x) in %d ms (%08x)\n",
  325. Cmd->Command, Cmd, Cmd->TimeoutCommand, Cmd->Timeout, Cmd->WaitFlags);
  326. //
  327. // Remove from list
  328. //
  329. if (CmdWaitFlagIs(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST)) {
  330. RemoveEntryListB(&Cmd->ListEntry);
  331. ClearCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
  332. }
  333. //
  334. // Is the queue rundown?
  335. //
  336. if (WaitIsRunDown) {
  337. DPRINT2(4, "Can't insert cmd %08x (%08x); queue rundown\n",
  338. Cmd->Command, Cmd);
  339. WStatus = ERROR_ACCESS_DENIED;
  340. goto CLEANUP;
  341. }
  342. //
  343. // Insert into empty list
  344. //
  345. if (IsListEmpty(&WaitList)) {
  346. InsertHeadList(&WaitList, &Cmd->ListEntry);
  347. SetCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
  348. goto CLEANUP;
  349. }
  350. //
  351. // Insert at tail
  352. //
  353. Entry = GetListTail(&WaitList);
  354. OldCmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
  355. if (OldCmd->WaitFileTime <= Cmd->WaitFileTime) {
  356. InsertTailList(&WaitList, &Cmd->ListEntry);
  357. SetCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
  358. goto CLEANUP;
  359. }
  360. //
  361. // Insert into list
  362. //
  363. for (Entry = GetListHead(&WaitList);
  364. Entry != &WaitList;
  365. Entry = GetListNext(Entry)) {
  366. OldCmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
  367. if (Cmd->WaitFileTime <= OldCmd->WaitFileTime) {
  368. InsertTailList(Entry, &Cmd->ListEntry);
  369. SetCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
  370. goto CLEANUP;
  371. }
  372. }
  373. CLEANUP:
  374. //
  375. // Reset the timer if the expiration time has changed
  376. //
  377. if (WIN_SUCCESS(WStatus)) {
  378. WaitReset(FALSE);
  379. }
  380. LeaveCriticalSection(&WaitLock);
  381. return WStatus;
  382. }
  383. VOID
  384. WaitTimeout(
  385. VOID
  386. )
  387. /*++
  388. Routine Description:
  389. Expel the commands whose timeouts have passed.
  390. Arguments:
  391. None.
  392. Return Value:
  393. None.
  394. --*/
  395. {
  396. #undef DEBSUB
  397. #define DEBSUB "WaitTimeout:"
  398. PLIST_ENTRY Entry;
  399. PCOMMAND_PACKET Cmd;
  400. LONGLONG Now;
  401. //
  402. // Expel expired commands
  403. //
  404. FrsNowAsFileTime(&Now);
  405. EnterCriticalSection(&WaitLock);
  406. while (!IsListEmpty(&WaitList)) {
  407. Entry = GetListHead(&WaitList);
  408. Cmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
  409. //
  410. // Hasn't timed out; stop
  411. //
  412. if ((Cmd->WaitFileTime - WAIT_FUZZY_TIMEOUT) > Now) {
  413. break;
  414. }
  415. //
  416. // Timed out; process it. Be careful to synchronize with
  417. // WaitUnsubmit.
  418. //
  419. RemoveEntryListB(Entry);
  420. ClearCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
  421. EnterCriticalSection(&WaitUnsubmitLock);
  422. LeaveCriticalSection(&WaitLock);
  423. WaitProcessCommand(Cmd, ERROR_SUCCESS);
  424. LeaveCriticalSection(&WaitUnsubmitLock);
  425. EnterCriticalSection(&WaitLock);
  426. }
  427. //
  428. // Reset the timer (always)
  429. //
  430. WaitReset(TRUE);
  431. LeaveCriticalSection(&WaitLock);
  432. }
  433. VOID
  434. WaitRunDownList(
  435. VOID
  436. )
  437. /*++
  438. Routine Description:
  439. Error off the commands in the timeout list
  440. Arguments:
  441. None.
  442. Return Value:
  443. None.
  444. --*/
  445. {
  446. #undef DEBSUB
  447. #define DEBSUB "WaitRunDownList:"
  448. PLIST_ENTRY Entry;
  449. PCOMMAND_PACKET Cmd;
  450. //
  451. // Rundown commands
  452. //
  453. EnterCriticalSection(&WaitLock);
  454. while (!IsListEmpty(&WaitList)) {
  455. Entry = GetListHead(&WaitList);
  456. Cmd = CONTAINING_RECORD(Entry, COMMAND_PACKET, ListEntry);
  457. RemoveEntryListB(Entry);
  458. ClearCmdWaitFlag(Cmd, CMD_PKT_WAIT_FLAGS_ONLIST);
  459. EnterCriticalSection(&WaitUnsubmitLock);
  460. LeaveCriticalSection(&WaitLock);
  461. WaitProcessCommand(Cmd, ERROR_ACCESS_DENIED);
  462. LeaveCriticalSection(&WaitUnsubmitLock);
  463. EnterCriticalSection(&WaitLock);
  464. }
  465. FrsNowAsFileTime(&WaitFileTime);
  466. DPRINT1(4, "Resetting rundown timer to %08x %08x.\n", PRINTQUAD(WaitFileTime));
  467. SetWaitableTimer(WaitableTimer, (LARGE_INTEGER *)&WaitFileTime, 0, NULL, NULL, TRUE);
  468. WaitIsRunDown = TRUE;
  469. LeaveCriticalSection(&WaitLock);
  470. }
  471. DWORD
  472. MainWait(
  473. PFRS_THREAD FrsThread
  474. )
  475. /*++
  476. Routine Description:
  477. Entry point for a thread serving the wait queue.
  478. A timeout list is managed by a thread waiting on a
  479. waitable timer.
  480. The timer can be adjusted without context switching to the
  481. thread that is waiting on the timer.
  482. An entry can be pulled off the list. The timer is adjusted
  483. if the entry was at the head of the queue.
  484. The queue is sorted by timeout value. The timeout value is
  485. an absolute filetime.
  486. The list entry is a command packet. The generic command
  487. packet contains a field for the wait time in milliseconds.
  488. This code takes the wait time and converts it into an
  489. absolute filetime when the command packet is put on the
  490. queue. The timeout triggers when the time is equal to or
  491. greater than the command packet's filetime.
  492. Arguments:
  493. Arg - thread
  494. Return Value:
  495. None.
  496. --*/
  497. {
  498. #undef DEBSUB
  499. #define DEBSUB "MainWait:"
  500. HANDLE WaitArray[2];
  501. //
  502. // Thread is pointing at the correct queue
  503. //
  504. FRS_ASSERT(FrsThread->Data == &WaitList);
  505. DPRINT(0, "Wait thread has started.\n");
  506. again:
  507. //
  508. // Wait for work, an exit timeout, or the queue to be rundown
  509. //
  510. DPRINT(4, "Wait thread is waiting.\n");
  511. WaitArray[0] = WaitRunDown;
  512. WaitArray[1] = WaitableTimer;
  513. WaitForMultipleObjectsEx(2, WaitArray, FALSE, INFINITE, TRUE);
  514. DPRINT(4, "Wait thread is running.\n");
  515. //
  516. // Nothing to do; exit
  517. //
  518. EnterCriticalSection(&WaitLock);
  519. if (IsListEmpty(&WaitList)) {
  520. WaitIsRunning = FALSE;
  521. LeaveCriticalSection(&WaitLock);
  522. DPRINT(0, "Wait thread is exiting.\n");
  523. ThSupSubmitThreadExitCleanup(FrsThread);
  524. ExitThread(ERROR_SUCCESS);
  525. }
  526. LeaveCriticalSection(&WaitLock);
  527. //
  528. // Check for timed out commands
  529. //
  530. WaitTimeout();
  531. //
  532. // Continue forever
  533. //
  534. goto again;
  535. return ERROR_SUCCESS;
  536. }
  537. VOID
  538. WaitInitialize(
  539. VOID
  540. )
  541. /*++
  542. Routine Description:
  543. Initialize the wait subsystem. The thread is kicked off
  544. on demand.
  545. Arguments:
  546. None.
  547. Return Value:
  548. None.
  549. --*/
  550. {
  551. #undef DEBSUB
  552. #define DEBSUB "WaitInitialize:"
  553. //
  554. // Timeout list
  555. //
  556. InitializeListHead(&WaitList);
  557. InitializeCriticalSection(&WaitLock);
  558. InitializeCriticalSection(&WaitUnsubmitLock);
  559. //
  560. // Rundown event for list
  561. //
  562. WaitRunDown = FrsCreateEvent(TRUE, FALSE);
  563. //
  564. // Timer
  565. //
  566. FrsNowAsFileTime(&WaitFileTime);
  567. WaitableTimer = FrsCreateWaitableTimer(TRUE);
  568. DPRINT1(4, "Setting initial timer to %08x %08x.\n", PRINTQUAD(WaitFileTime));
  569. if (!SetWaitableTimer(WaitableTimer, (LARGE_INTEGER *)&WaitFileTime, 0, NULL, NULL, TRUE)) {
  570. DPRINT_WS(0, "ERROR - Resetting timer;", GetLastError());
  571. }
  572. }
  573. VOID
  574. ShutDownWait(
  575. VOID
  576. )
  577. /*++
  578. Routine Description:
  579. Shutdown the wait subsystem
  580. Arguments:
  581. None.
  582. Return Value:
  583. None.
  584. --*/
  585. {
  586. #undef DEBSUB
  587. #define DEBSUB "ShutDownWait:"
  588. WaitRunDownList();
  589. }