/// <summary>
/// <para>Thread for the timer. Ignores all exceptions. If no activity occurs for a while,
/// the thread will shut down.</para>
/// </summary>
private static void ThreadProc()
{
if (NetEventSource.IsEnabled)
{
NetEventSource.Enter(null);
}
#if DEBUG
DebugThreadTracking.SetThreadSource(ThreadKinds.Timer);
using (DebugThreadTracking.SetThreadKind(ThreadKinds.System | ThreadKinds.Async))
{
#endif
// Set this thread as a background thread. On AppDomain/Process shutdown, the thread will just be killed.
Thread.CurrentThread.IsBackground = true;
// Keep a permanent lock on s_Queues. This lets for example Shutdown() know when this thread isn't running.
lock (s_Queues)
{
// If shutdown was recently called, abort here.
if (Interlocked.CompareExchange(ref s_ThreadState, (int)TimerThreadState.Running, (int)TimerThreadState.Running) !=
(int)TimerThreadState.Running)
{
return;
}
bool running = true;
while (running)
{
try
{
s_ThreadReadyEvent.Reset();
while (true)
{
// Copy all the new queues to the real queues. Since only this thread modifies the real queues, it doesn't have to lock it.
if (s_NewQueues.Count > 0)
{
lock (s_NewQueues)
{
for (LinkedListNode <WeakReference> node = s_NewQueues.First; node != null; node = s_NewQueues.First)
{
s_NewQueues.Remove(node);
s_Queues.AddLast(node);
}
}
}
int now = Environment.TickCount;
int nextTick = 0;
bool haveNextTick = false;
for (LinkedListNode <WeakReference> node = s_Queues.First; node != null; /* node = node.Next must be done in the body */)
{
TimerQueue queue = (TimerQueue)node.Value.Target;
if (queue == null)
{
LinkedListNode <WeakReference> next = node.Next;
s_Queues.Remove(node);
node = next;
continue;
}
// Fire() will always return values that should be interpreted as later than 'now' (that is, even if 'now' is
// returned, it is 0x100000000 milliseconds in the future). There's also a chance that Fire() will return a value
// intended as > 0x100000000 milliseconds from 'now'. Either case will just cause an extra scan through the timers.
int nextTickInstance;
if (queue.Fire(out nextTickInstance) && (!haveNextTick || IsTickBetween(now, nextTick, nextTickInstance)))
{
nextTick = nextTickInstance;
haveNextTick = true;
}
node = node.Next;
}
// Figure out how long to wait, taking into account how long the loop took.
// Add 15 ms to compensate for poor TickCount resolution (want to guarantee a firing).
int newNow = Environment.TickCount;
int waitDuration = haveNextTick ?
(int)(IsTickBetween(now, nextTick, newNow) ?
Math.Min(unchecked ((uint)(nextTick - newNow)), (uint)(Int32.MaxValue - c_TickCountResolution)) + c_TickCountResolution :
0) :
c_ThreadIdleTimeoutMilliseconds;
if (NetEventSource.IsEnabled)
{
NetEventSource.Info(null, $"Waiting for {waitDuration}ms");
}
int waitResult = WaitHandle.WaitAny(s_ThreadEvents, waitDuration, false);
// 0 is s_ThreadShutdownEvent - die.
if (waitResult == 0)
{
if (NetEventSource.IsEnabled)
{
NetEventSource.Info(null, "Awoke, cause: Shutdown");
}
running = false;
break;
}
if (NetEventSource.IsEnabled)
{
NetEventSource.Info(null, $"Awoke, cause {(waitResult == WaitHandle.WaitTimeout ? "Timeout" : "Prod")}");
}
// If we timed out with nothing to do, shut down.
if (waitResult == WaitHandle.WaitTimeout && !haveNextTick)
{
Interlocked.CompareExchange(ref s_ThreadState, (int)TimerThreadState.Idle, (int)TimerThreadState.Running);
// There could have been one more prod between the wait and the exchange. Check, and abort if necessary.
if (s_ThreadReadyEvent.WaitOne(0, false))
{
if (Interlocked.CompareExchange(ref s_ThreadState, (int)TimerThreadState.Running, (int)TimerThreadState.Idle) ==
(int)TimerThreadState.Idle)
{
continue;
}
}
running = false;
break;
}
}
}
catch (Exception exception)
{
if (ExceptionCheck.IsFatal(exception))
{
throw;
}
if (NetEventSource.IsEnabled)
{
NetEventSource.Error(null, exception);
}
// The only options are to continue processing and likely enter an error-loop,
// shut down timers for this AppDomain, or shut down the AppDomain. Go with shutting
// down the AppDomain in debug, and going into a loop in retail, but try to make the
// loop somewhat slow. Note that in retail, this can only be triggered by OutOfMemory or StackOverflow,
// or an exception thrown within TimerThread - the rest are caught in Fire().
#if !DEBUG
Thread.Sleep(1000);
#else
throw;
#endif
}
}
}
if (NetEventSource.IsEnabled)
{
NetEventSource.Info(null, "Stop");
}
#if DEBUG
}
#endif
}