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