The Labs \ Source Viewer \ SSCLI \ System.Net \ TimerState

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

Developer Fusion