PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/WCFWebApi/src/System.Net.Http/System/Net/_TimerThread.cs

#
C# | 882 lines | 511 code | 101 blank | 270 comment | 78 complexity | c6ed610fa278f6c20c4239bb24478c35 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. //------------------------------------------------------------------------------
  2. // <copyright file="_TimerThread.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. //------------------------------------------------------------------------------
  6. namespace System.Net
  7. {
  8. using System.Collections;
  9. using System.Collections.Generic;
  10. using System.Diagnostics.CodeAnalysis;
  11. using System.Globalization;
  12. using System.Runtime.InteropServices;
  13. using System.Threading;
  14. /// <summary>
  15. /// <para>Acts as countdown timer, used to measure elapsed time over a sync operation.</para>
  16. /// </summary>
  17. internal static class TimerThread
  18. {
  19. /// <summary>
  20. /// <para>Represents a queue of timers, which all have the same duration.</para>
  21. /// </summary>
  22. internal abstract class Queue
  23. {
  24. private readonly int m_DurationMilliseconds;
  25. internal Queue(int durationMilliseconds)
  26. {
  27. m_DurationMilliseconds = durationMilliseconds;
  28. }
  29. /// <summary>
  30. /// <para>The duration in milliseconds of timers in this queue.</para>
  31. /// </summary>
  32. internal int Duration
  33. {
  34. get
  35. {
  36. return m_DurationMilliseconds;
  37. }
  38. }
  39. /// <summary>
  40. /// <para>Creates and returns a handle to a new polled timer.</para>
  41. /// </summary>
  42. internal Timer CreateTimer()
  43. {
  44. return CreateTimer(null, null);
  45. }
  46. /*
  47. // Consider removing.
  48. /// <summary>
  49. /// <para>Creates and returns a handle to a new timer.</para>
  50. /// </summary>
  51. internal Timer CreateTimer(Callback callback) {
  52. return CreateTimer(callback, null);
  53. }
  54. */
  55. /// <summary>
  56. /// <para>Creates and returns a handle to a new timer with attached context.</para>
  57. /// </summary>
  58. internal abstract Timer CreateTimer(Callback callback, object context);
  59. }
  60. /// <summary>
  61. /// <para>Represents a timer and provides a mechanism to cancel.</para>
  62. /// </summary>
  63. internal abstract class Timer : IDisposable
  64. {
  65. private readonly int m_StartTimeMilliseconds;
  66. private readonly int m_DurationMilliseconds;
  67. internal Timer(int durationMilliseconds)
  68. {
  69. m_DurationMilliseconds = durationMilliseconds;
  70. m_StartTimeMilliseconds = Environment.TickCount;
  71. }
  72. /// <summary>
  73. /// <para>The duration in milliseconds of timer.</para>
  74. /// </summary>
  75. internal int Duration
  76. {
  77. get
  78. {
  79. return m_DurationMilliseconds;
  80. }
  81. }
  82. /// <summary>
  83. /// <para>The time (relative to Environment.TickCount) when the timer started.</para>
  84. /// </summary>
  85. internal int StartTime
  86. {
  87. get
  88. {
  89. return m_StartTimeMilliseconds;
  90. }
  91. }
  92. /// <summary>
  93. /// <para>The time (relative to Environment.TickCount) when the timer will expire.</para>
  94. /// </summary>
  95. internal int Expiration
  96. {
  97. get
  98. {
  99. return unchecked(m_StartTimeMilliseconds + m_DurationMilliseconds);
  100. }
  101. }
  102. /*
  103. // Consider removing.
  104. /// <summary>
  105. /// <para>The amount of time the timer has been running. If it equals Duration, it has fired. 1 less means it has expired but
  106. /// not yet fired. Int32.MaxValue is the ceiling - the actual value could be longer. In the case of infinite timers, this
  107. /// value becomes unreliable when TickCount wraps (about 46 days).</para>
  108. /// </summary>
  109. internal int Elapsed {
  110. get {
  111. if (HasExpired || Duration == 0) {
  112. return Duration;
  113. }
  114. int now = Environment.TickCount;
  115. if (Duration == TimeoutInfinite)
  116. {
  117. return (int) (Math.Min((uint) unchecked(now - StartTime), (uint) Int32.MaxValue);
  118. }
  119. else
  120. {
  121. return (IsTickBetween(StartTime, Expiration, now) && Duration > 1) ?
  122. (int) (Math.Min((uint) unchecked(now - StartTime), Duration - 2) : Duration - 1;
  123. }
  124. }
  125. }
  126. */
  127. /// <summary>
  128. /// <para>The amount of time left on the timer. 0 means it has fired. 1 means it has expired but
  129. /// not yet fired. -1 means infinite. Int32.MaxValue is the ceiling - the actual value could be longer.</para>
  130. /// </summary>
  131. internal int TimeRemaining
  132. {
  133. get
  134. {
  135. if (HasExpired)
  136. {
  137. return 0;
  138. }
  139. if (Duration == Timeout.Infinite)
  140. {
  141. return Timeout.Infinite;
  142. }
  143. int now = Environment.TickCount;
  144. int remaining = IsTickBetween(StartTime, Expiration, now) ?
  145. (int)(Math.Min((uint)unchecked(Expiration - now), (uint)Int32.MaxValue)) : 0;
  146. return remaining < 2 ? remaining + 1 : remaining;
  147. }
  148. }
  149. /// <summary>
  150. /// <para>Cancels the timer. Returns true if the timer hasn't and won't fire; false if it has or will.</para>
  151. /// </summary>
  152. internal abstract bool Cancel();
  153. /// <summary>
  154. /// <para>Whether or not the timer has expired.</para>
  155. /// </summary>
  156. internal abstract bool HasExpired { get; }
  157. public void Dispose()
  158. {
  159. Cancel();
  160. }
  161. }
  162. /// <summary>
  163. /// <para>Prototype for the callback that is called when a timer expires.</para>
  164. /// </summary>
  165. internal delegate void Callback(Timer timer, int timeNoticed, object context);
  166. private const int c_ThreadIdleTimeoutMilliseconds = 30 * 1000;
  167. private const int c_CacheScanPerIterations = 32;
  168. private const int c_TickCountResolution = 15;
  169. private static LinkedList<WeakReference> s_Queues = new LinkedList<WeakReference>();
  170. private static LinkedList<WeakReference> s_NewQueues = new LinkedList<WeakReference>();
  171. private static int s_ThreadState = (int)TimerThreadState.Idle; // Really a TimerThreadState, but need an int for Interlocked.
  172. private static AutoResetEvent s_ThreadReadyEvent = new AutoResetEvent(false);
  173. private static ManualResetEvent s_ThreadShutdownEvent = new ManualResetEvent(false);
  174. private static WaitHandle[] s_ThreadEvents;
  175. private static int s_CacheScanIteration;
  176. private static Hashtable s_QueuesCache = new Hashtable();
  177. static TimerThread()
  178. {
  179. s_ThreadEvents = new WaitHandle[] { s_ThreadShutdownEvent, s_ThreadReadyEvent };
  180. AppDomain.CurrentDomain.DomainUnload += new EventHandler(OnDomainUnload);
  181. }
  182. /// <summary>
  183. /// <para>The possible states of the timer thread.</para>
  184. /// </summary>
  185. private enum TimerThreadState
  186. {
  187. Idle,
  188. Running,
  189. Stopped
  190. }
  191. /// <summary>
  192. /// <para>The main external entry-point, allows creating new timer queues.</para>
  193. /// </summary>
  194. internal static Queue CreateQueue(int durationMilliseconds)
  195. {
  196. if (durationMilliseconds == Timeout.Infinite)
  197. {
  198. return new InfiniteTimerQueue();
  199. }
  200. if (durationMilliseconds < 0)
  201. {
  202. throw new ArgumentOutOfRangeException("durationMilliseconds");
  203. }
  204. // Queues are held with a weak reference so they can simply be abandoned and automatically cleaned up.
  205. TimerQueue queue;
  206. lock (s_NewQueues)
  207. {
  208. queue = new TimerQueue(durationMilliseconds);
  209. WeakReference weakQueue = new WeakReference(queue);
  210. s_NewQueues.AddLast(weakQueue);
  211. }
  212. return queue;
  213. }
  214. /// <summary>
  215. /// <para>Alternative cache-based queue factory. Always synchronized.</para>
  216. /// </summary>
  217. internal static Queue GetOrCreateQueue(int durationMilliseconds)
  218. {
  219. if (durationMilliseconds == Timeout.Infinite)
  220. {
  221. return new InfiniteTimerQueue();
  222. }
  223. if (durationMilliseconds < 0)
  224. {
  225. throw new ArgumentOutOfRangeException("durationMilliseconds");
  226. }
  227. TimerQueue queue;
  228. WeakReference weakQueue = (WeakReference)s_QueuesCache[durationMilliseconds];
  229. if (weakQueue == null || (queue = (TimerQueue)weakQueue.Target) == null)
  230. {
  231. lock (s_NewQueues)
  232. {
  233. weakQueue = (WeakReference)s_QueuesCache[durationMilliseconds];
  234. if (weakQueue == null || (queue = (TimerQueue)weakQueue.Target) == null)
  235. {
  236. queue = new TimerQueue(durationMilliseconds);
  237. weakQueue = new WeakReference(queue);
  238. s_NewQueues.AddLast(weakQueue);
  239. s_QueuesCache[durationMilliseconds] = weakQueue;
  240. // Take advantage of this lock to periodically scan the table for garbage.
  241. if (++s_CacheScanIteration % c_CacheScanPerIterations == 0)
  242. {
  243. List<int> garbage = new List<int>();
  244. foreach (DictionaryEntry pair in s_QueuesCache)
  245. {
  246. if (((WeakReference)pair.Value).Target == null)
  247. {
  248. garbage.Add((int)pair.Key);
  249. }
  250. }
  251. for (int i = 0; i < garbage.Count; i++)
  252. {
  253. s_QueuesCache.Remove(garbage[i]);
  254. }
  255. }
  256. }
  257. }
  258. }
  259. return queue;
  260. }
  261. /// <summary>
  262. /// <para>Represents a queue of timers of fixed duration.</para>
  263. /// </summary>
  264. [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "code ported from WCF.")]
  265. private class TimerQueue : Queue
  266. {
  267. // This is a GCHandle that holds onto the TimerQueue when active timers are in it.
  268. // The TimerThread only holds WeakReferences to it so that it can be collected when the user lets go of it.
  269. // But we don't want the user to HAVE to keep a reference to it when timers are active in it.
  270. // It gets created when the first timer gets added, and cleaned up when the TimerThread notices it's empty.
  271. // The TimerThread will always notice it's empty eventually, since the TimerThread will always wake up and
  272. // try to fire the timer, even if it was cancelled and removed prematurely.
  273. private IntPtr m_ThisHandle;
  274. // This sentinel TimerNode acts as both the head and the tail, allowing nodes to go in and out of the list without updating
  275. // any TimerQueue members. m_Timers.Next is the true head, and .Prev the true tail. This also serves as the list's lock.
  276. private readonly TimerNode m_Timers;
  277. /// <summary>
  278. /// <para>Create a new TimerQueue. TimerQueues must be created while s_NewQueues is locked in
  279. /// order to synchronize with Shutdown().</para>
  280. /// </summary>
  281. /// <param name="durationMilliseconds"></param>
  282. internal TimerQueue(int durationMilliseconds) :
  283. base(durationMilliseconds)
  284. {
  285. // Create the doubly-linked list with a sentinel head and tail so that this member never needs updating.
  286. m_Timers = new TimerNode();
  287. m_Timers.Next = m_Timers;
  288. m_Timers.Prev = m_Timers;
  289. // If ReleaseHandle comes back, we need something like this here.
  290. // m_HandleFrozen = s_ThreadState == (int) TimerThreadState.Stopped ? 1 : 0;
  291. }
  292. /// <summary>
  293. /// <para>Creates new timers. This method is thread-safe.</para>
  294. /// </summary>
  295. [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "code is clone of System.Net.Http")]
  296. internal override Timer CreateTimer(Callback callback, object context)
  297. {
  298. TimerNode timer = new TimerNode(callback, context, Duration, m_Timers);
  299. // Add this on the tail. (Actually, one before the tail - m_Timers is the sentinel tail.)
  300. bool needProd = false;
  301. lock (m_Timers)
  302. {
  303. // If this is the first timer in the list, we need to create a queue handle and prod the timer thread.
  304. if (m_Timers.Next == m_Timers)
  305. {
  306. if (m_ThisHandle == IntPtr.Zero)
  307. {
  308. m_ThisHandle = (IntPtr)GCHandle.Alloc(this);
  309. }
  310. needProd = true;
  311. }
  312. timer.Next = m_Timers;
  313. timer.Prev = m_Timers.Prev;
  314. m_Timers.Prev.Next = timer;
  315. m_Timers.Prev = timer;
  316. }
  317. // If, after we add the new tail, there is a chance that the tail is the next
  318. // node to be processed, we need to wake up the timer thread.
  319. if (needProd)
  320. {
  321. TimerThread.Prod();
  322. }
  323. return timer;
  324. }
  325. /// <summary>
  326. /// <para>Called by the timer thread to fire the expired timers. Returns true if there are future timers
  327. /// in the queue, and if so, also sets nextExpiration.</para>
  328. /// </summary>
  329. internal bool Fire(out int nextExpiration)
  330. {
  331. while (true)
  332. {
  333. // Check if we got to the end. If so, free the handle.
  334. TimerNode timer = m_Timers.Next;
  335. if (timer == m_Timers)
  336. {
  337. lock (m_Timers)
  338. {
  339. timer = m_Timers.Next;
  340. if (timer == m_Timers)
  341. {
  342. if (m_ThisHandle != IntPtr.Zero)
  343. {
  344. ((GCHandle)m_ThisHandle).Free();
  345. m_ThisHandle = IntPtr.Zero;
  346. }
  347. nextExpiration = 0;
  348. return false;
  349. }
  350. }
  351. }
  352. if (!timer.Fire())
  353. {
  354. nextExpiration = timer.Expiration;
  355. return true;
  356. }
  357. }
  358. }
  359. /* Currently unused. If revived, needs to be changed to the new design of m_ThisHandle.
  360. /// <summary>
  361. /// <para>Release the GCHandle to this object, and prevent it from ever being allocated again.</para>
  362. /// </summary>
  363. internal void ReleaseHandle()
  364. {
  365. if (Interlocked.Exchange(ref m_HandleFrozen, 1) == 1) {
  366. return;
  367. }
  368. // Add a fake timer to the count. This will prevent the count ever again reaching zero, effectively
  369. // disabling the GCHandle alloc/free logic. If it finds that one is allocated, deallocate it.
  370. if (Interlocked.Increment(ref m_ActiveTimerCount) != 1) {
  371. IntPtr handle;
  372. while ((handle = Interlocked.Exchange(ref m_ThisHandle, IntPtr.Zero)) == IntPtr.Zero)
  373. {
  374. Thread.SpinWait(1);
  375. }
  376. ((GCHandle)handle).Free();
  377. }
  378. }
  379. */
  380. }
  381. /// <summary>
  382. /// <para>A special dummy implementation for a queue of timers of infinite duration.</para>
  383. /// </summary>
  384. private class InfiniteTimerQueue : Queue
  385. {
  386. internal InfiniteTimerQueue() : base(Timeout.Infinite) { }
  387. /// <summary>
  388. /// <para>Always returns a dummy infinite timer.</para>
  389. /// </summary>
  390. internal override Timer CreateTimer(Callback callback, object context)
  391. {
  392. return new InfiniteTimer();
  393. }
  394. }
  395. /// <summary>
  396. /// <para>Internal representation of an individual timer.</para>
  397. /// </summary>
  398. private class TimerNode : Timer
  399. {
  400. private TimerState m_TimerState;
  401. private Callback m_Callback;
  402. private object m_Context;
  403. private object m_QueueLock;
  404. private TimerNode next;
  405. private TimerNode prev;
  406. /// <summary>
  407. /// <para>Status of the timer.</para>
  408. /// </summary>
  409. private enum TimerState
  410. {
  411. Ready,
  412. Fired,
  413. Cancelled,
  414. Sentinel
  415. }
  416. internal TimerNode(Callback callback, object context, int durationMilliseconds, object queueLock)
  417. : base(durationMilliseconds)
  418. {
  419. if (callback != null)
  420. {
  421. m_Callback = callback;
  422. m_Context = context;
  423. }
  424. m_TimerState = TimerState.Ready;
  425. m_QueueLock = queueLock;
  426. }
  427. // A sentinel node - both the head and tail are one, which prevent the head and tail from ever having to be updated.
  428. internal TimerNode()
  429. : base(0)
  430. {
  431. m_TimerState = TimerState.Sentinel;
  432. }
  433. /*
  434. // Consider removing.
  435. internal bool IsDead
  436. {
  437. get
  438. {
  439. return m_TimerState != TimerState.Ready;
  440. }
  441. }
  442. */
  443. internal override bool HasExpired
  444. {
  445. get
  446. {
  447. return m_TimerState == TimerState.Fired;
  448. }
  449. }
  450. internal TimerNode Next
  451. {
  452. get
  453. {
  454. return next;
  455. }
  456. set
  457. {
  458. next = value;
  459. }
  460. }
  461. internal TimerNode Prev
  462. {
  463. get
  464. {
  465. return prev;
  466. }
  467. set
  468. {
  469. prev = value;
  470. }
  471. }
  472. /// <summary>
  473. /// <para>Cancels the timer. Returns true if it hasn't and won't fire; false if it has or will, or has already been cancelled.</para>
  474. /// </summary>
  475. internal override bool Cancel()
  476. {
  477. if (m_TimerState == TimerState.Ready)
  478. {
  479. lock (m_QueueLock)
  480. {
  481. if (m_TimerState == TimerState.Ready)
  482. {
  483. // Remove it from the list. This keeps the list from getting to big when there are a lot of rapid creations
  484. // and cancellations. This is done before setting it to Cancelled to try to prevent the Fire() loop from
  485. // seeing it, or if it does, of having to take a lock to synchronize with the state of the list.
  486. Next.Prev = Prev;
  487. Prev.Next = Next;
  488. // Just cleanup. Doesn't need to be in the lock but is easier to have here.
  489. Next = null;
  490. Prev = null;
  491. m_Callback = null;
  492. m_Context = null;
  493. m_TimerState = TimerState.Cancelled;
  494. return true;
  495. }
  496. }
  497. }
  498. return false;
  499. }
  500. /// <summary>
  501. /// <para>Fires the timer if it is still active and has expired. Returns
  502. /// true if it can be deleted, or false if it is still timing.</para>
  503. /// </summary>
  504. internal bool Fire()
  505. {
  506. if (m_TimerState != TimerState.Ready)
  507. {
  508. return true;
  509. }
  510. // Must get the current tick count within this method so it is guaranteed not to be before
  511. // StartTime, which is set in the constructor.
  512. int nowMilliseconds = Environment.TickCount;
  513. if (IsTickBetween(StartTime, Expiration, nowMilliseconds))
  514. {
  515. return false;
  516. }
  517. bool needCallback = false;
  518. lock (m_QueueLock)
  519. {
  520. if (m_TimerState == TimerState.Ready)
  521. {
  522. m_TimerState = TimerState.Fired;
  523. // Remove it from the list.
  524. Next.Prev = Prev;
  525. Prev.Next = Next;
  526. // Doesn't need to be in the lock but is easier to have here.
  527. Next = null;
  528. Prev = null;
  529. needCallback = m_Callback != null;
  530. }
  531. }
  532. if (needCallback)
  533. {
  534. try
  535. {
  536. Callback callback = m_Callback;
  537. object context = m_Context;
  538. m_Callback = null;
  539. m_Context = null;
  540. callback(this, nowMilliseconds, context);
  541. }
  542. catch (Exception exception)
  543. {
  544. if (NclUtilities.IsFatal(exception)) throw;
  545. if (Logging.On) Logging.PrintError(Logging.Web, "TimerThreadTimer#" + StartTime.ToString(NumberFormatInfo.InvariantInfo) + "::Fire() - " + exception);
  546. // This thread is not allowed to go into user code, so we should never get an exception here.
  547. // So, in debug, throw it up, killing the AppDomain. In release, we'll just ignore it.
  548. #if DEBUG
  549. throw;
  550. #endif
  551. }
  552. }
  553. return true;
  554. }
  555. }
  556. /// <summary>
  557. /// <para>A dummy infinite timer.</para>
  558. /// </summary>
  559. private class InfiniteTimer : Timer
  560. {
  561. internal InfiniteTimer() : base(Timeout.Infinite) { }
  562. private int cancelled;
  563. internal override bool HasExpired
  564. {
  565. get
  566. {
  567. return false;
  568. }
  569. }
  570. /// <summary>
  571. /// <para>Cancels the timer. Returns true the first time, false after that.</para>
  572. /// </summary>
  573. internal override bool Cancel()
  574. {
  575. return Interlocked.Exchange(ref cancelled, 1) == 0;
  576. }
  577. }
  578. /// <summary>
  579. /// <para>Internal mechanism used when timers are added to wake up / create the thread.</para>
  580. /// </summary>
  581. private static void Prod()
  582. {
  583. s_ThreadReadyEvent.Set();
  584. TimerThreadState oldState = (TimerThreadState)Interlocked.CompareExchange(
  585. ref s_ThreadState,
  586. (int)TimerThreadState.Running,
  587. (int)TimerThreadState.Idle);
  588. if (oldState == TimerThreadState.Idle)
  589. {
  590. new Thread(new ThreadStart(ThreadProc)).Start();
  591. }
  592. }
  593. /// <summary>
  594. /// <para>Thread for the timer. Ignores all exceptions except ThreadAbort. If no activity occurs for a while,
  595. /// the thread will shut down.</para>
  596. /// </summary>
  597. private static void ThreadProc()
  598. {
  599. // t_IsTimerThread = true; -- Not used anywhere.
  600. // Set this thread as a background thread. On AppDomain/Process shutdown, the thread will just be killed.
  601. Thread.CurrentThread.IsBackground = true;
  602. // Keep a permanent lock on s_Queues. This lets for example Shutdown() know when this thread isn't running.
  603. lock (s_Queues)
  604. {
  605. // If shutdown was recently called, abort here.
  606. if (Interlocked.CompareExchange(ref s_ThreadState, (int)TimerThreadState.Running, (int)TimerThreadState.Running) !=
  607. (int)TimerThreadState.Running)
  608. {
  609. return;
  610. }
  611. bool running = true;
  612. while (running)
  613. {
  614. try
  615. {
  616. s_ThreadReadyEvent.Reset();
  617. while (true)
  618. {
  619. // Copy all the new queues to the real queues. Since only this thread modifies the real queues, it doesn't have to lock it.
  620. if (s_NewQueues.Count > 0)
  621. {
  622. lock (s_NewQueues)
  623. {
  624. for (LinkedListNode<WeakReference> node = s_NewQueues.First; node != null; node = s_NewQueues.First)
  625. {
  626. s_NewQueues.Remove(node);
  627. s_Queues.AddLast(node);
  628. }
  629. }
  630. }
  631. int now = Environment.TickCount;
  632. int nextTick = 0;
  633. bool haveNextTick = false;
  634. for (LinkedListNode<WeakReference> node = s_Queues.First; node != null; /* node = node.Next must be done in the body */)
  635. {
  636. TimerQueue queue = (TimerQueue)node.Value.Target;
  637. if (queue == null)
  638. {
  639. LinkedListNode<WeakReference> next = node.Next;
  640. s_Queues.Remove(node);
  641. node = next;
  642. continue;
  643. }
  644. // Fire() will always return values that should be interpreted as later than 'now' (that is, even if 'now' is
  645. // returned, it is 0x100000000 milliseconds in the future). There's also a chance that Fire() will return a value
  646. // intended as > 0x100000000 milliseconds from 'now'. Either case will just cause an extra scan through the timers.
  647. int nextTickInstance;
  648. if (queue.Fire(out nextTickInstance) && (!haveNextTick || IsTickBetween(now, nextTick, nextTickInstance)))
  649. {
  650. nextTick = nextTickInstance;
  651. haveNextTick = true;
  652. }
  653. node = node.Next;
  654. }
  655. // Figure out how long to wait, taking into account how long the loop took.
  656. // Add 15 ms to compensate for poor TickCount resolution (want to guarantee a firing).
  657. int newNow = Environment.TickCount;
  658. int waitDuration = haveNextTick ?
  659. (int)(IsTickBetween(now, nextTick, newNow) ?
  660. Math.Min(unchecked((uint)(nextTick - newNow)), (uint)(Int32.MaxValue - c_TickCountResolution)) + c_TickCountResolution :
  661. 0) :
  662. c_ThreadIdleTimeoutMilliseconds;
  663. int waitResult = WaitHandle.WaitAny(s_ThreadEvents, waitDuration, false);
  664. // 0 is s_ThreadShutdownEvent - die.
  665. if (waitResult == 0)
  666. {
  667. running = false;
  668. break;
  669. }
  670. // If we timed out with nothing to do, shut down.
  671. if (waitResult == WaitHandle.WaitTimeout && !haveNextTick)
  672. {
  673. Interlocked.CompareExchange(ref s_ThreadState, (int)TimerThreadState.Idle, (int)TimerThreadState.Running);
  674. // There could have been one more prod between the wait and the exchange. Check, and abort if necessary.
  675. if (s_ThreadReadyEvent.WaitOne(0, false))
  676. {
  677. if (Interlocked.CompareExchange(ref s_ThreadState, (int)TimerThreadState.Running, (int)TimerThreadState.Idle) ==
  678. (int)TimerThreadState.Idle)
  679. {
  680. continue;
  681. }
  682. }
  683. running = false;
  684. break;
  685. }
  686. }
  687. }
  688. catch (Exception exception)
  689. {
  690. if (NclUtilities.IsFatal(exception)) throw;
  691. if (Logging.On) Logging.PrintError(Logging.Web, "TimerThread#" + Thread.CurrentThread.ManagedThreadId.ToString(NumberFormatInfo.InvariantInfo) + "::ThreadProc() - Exception:" + exception.ToString());
  692. // The only options are to continue processing and likely enter an error-loop,
  693. // shut down timers for this AppDomain, or shut down the AppDomain. Go with shutting
  694. // down the AppDomain in debug, and going into a loop in retail, but try to make the
  695. // loop somewhat slow. Note that in retail, this can only be triggered by OutOfMemory or StackOverflow,
  696. // or an thrown within TimerThread - the rest are caught in Fire().
  697. #if !DEBUG
  698. Thread.Sleep(1000);
  699. #else
  700. throw;
  701. #endif
  702. }
  703. }
  704. }
  705. }
  706. /* Currently unused.
  707. /// <summary>
  708. /// <para>Stops the timer thread and prevents a new one from forming. No more timers can expire.</para>
  709. /// </summary>
  710. internal static void Shutdown() {
  711. StopTimerThread();
  712. // As long as TimerQueues are always created and added to s_NewQueues within the same lock,
  713. // this should catch all existing TimerQueues (and all new onew will see s_ThreadState).
  714. lock (s_NewQueues) {
  715. foreach (WeakReference node in s_NewQueues) {
  716. TimerQueue queue = (TimerQueue)node.Target;
  717. if(queue != null) {
  718. queue.ReleaseHandle();
  719. }
  720. }
  721. }
  722. // Once that thread is gone, release all the remaining GCHandles.
  723. lock (s_Queues) {
  724. foreach (WeakReference node in s_Queues) {
  725. TimerQueue queue = (TimerQueue)node.Target;
  726. if(queue != null) {
  727. queue.ReleaseHandle();
  728. }
  729. }
  730. }
  731. }
  732. */
  733. private static void StopTimerThread()
  734. {
  735. Interlocked.Exchange(ref s_ThreadState, (int)TimerThreadState.Stopped);
  736. s_ThreadShutdownEvent.Set();
  737. }
  738. /// <summary>
  739. /// <para>Helper for deciding whether a given TickCount is before or after a given expiration
  740. /// tick count assuming that it can't be before a given starting TickCount.</para>
  741. /// </summary>
  742. private static bool IsTickBetween(int start, int end, int comparand)
  743. {
  744. // Assumes that if start and end are equal, they are the same time.
  745. // Assumes that if the comparand and start are equal, no time has passed,
  746. // and that if the comparand and end are equal, end has occurred.
  747. return ((start <= comparand) == (end <= comparand)) != (start <= end);
  748. }
  749. /// <summary>
  750. /// <para>When the AppDomain is shut down, the timer thread is stopped.</para>
  751. /// <summary>
  752. private static void OnDomainUnload(object sender, EventArgs e)
  753. {
  754. try
  755. {
  756. StopTimerThread();
  757. }
  758. catch { }
  759. }
  760. /*
  761. /// <summary>
  762. /// <para>This thread static can be used to tell whether the current thread is the TimerThread thread.</para>
  763. /// </summary>
  764. [ThreadStatic]
  765. private static bool t_IsTimerThread;
  766. // Consider removing.
  767. internal static bool IsTimerThread
  768. {
  769. get
  770. {
  771. return t_IsTimerThread;
  772. }
  773. }
  774. */
  775. }
  776. }