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