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