1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
7 namespace System.Threading
10 using System.Security;
11 using Microsoft.Win32;
12 using System.Runtime.CompilerServices;
13 using System.Runtime.InteropServices;
14 using System.Runtime.ConstrainedExecution;
15 using System.Runtime.Versioning;
16 using System.Diagnostics;
17 using System.Diagnostics.Tracing;
18 using Microsoft.Win32.SafeHandles;
22 public delegate void TimerCallback(Object state);
25 // TimerQueue maintains a list of active timers in this AppDomain. We use a single native timer, supplied by the VM,
26 // to schedule all managed timers in the AppDomain.
28 // Perf assumptions: We assume that timers are created and destroyed frequently, but rarely actually fire.
29 // There are roughly two types of timer:
31 // - timeouts for operations. These are created and destroyed very frequently, but almost never fire, because
32 // the whole point is that the timer only fires if something has gone wrong.
34 // - scheduled background tasks. These typically do fire, but they usually have quite long durations.
35 // So the impact of spending a few extra cycles to fire these is negligible.
37 // Because of this, we want to choose a data structure with very fast insert and delete times, but we can live
38 // with linear traversal times when firing timers.
40 // The data structure we've chosen is an unordered doubly-linked list of active timers. This gives O(1) insertion
41 // and removal, and O(N) traversal when finding expired timers.
43 // Note that all instance methods of this class require that the caller hold a lock on the TimerQueue instance.
45 internal class TimerQueue
47 #region Shared TimerQueue instances
49 public static TimerQueue[] Instances { get; } = CreateTimerQueues();
51 private TimerQueue(int id)
56 private static TimerQueue[] CreateTimerQueues()
58 var queues = new TimerQueue[Environment.ProcessorCount];
59 for (int i = 0; i < queues.Length; i++)
61 queues[i] = new TimerQueue(i);
68 #region interface to native per-AppDomain timer
71 // We need to keep our notion of time synchronized with the calls to SleepEx that drive
72 // the underlying native timer. In Win8, SleepEx does not count the time the machine spends
73 // sleeping/hibernating. Environment.TickCount (GetTickCount) *does* count that time,
74 // so we will get out of sync with SleepEx if we use that method.
76 // So, on Win8, we use QueryUnbiasedInterruptTime instead; this does not count time spent
77 // in sleep/hibernate mode.
79 private static int TickCount
84 if (Environment.IsWindows8OrAbove)
88 bool result = Win32Native.QueryUnbiasedInterruptTime(out time100ns);
90 throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
92 // convert to 100ns to milliseconds, and truncate to 32 bits.
93 return (int)(uint)(time100ns / 10000);
98 return Environment.TickCount;
104 // We use a SafeHandle to ensure that the native timer is destroyed when the AppDomain is unloaded.
106 private class AppDomainTimerSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
108 public AppDomainTimerSafeHandle()
113 protected override bool ReleaseHandle()
115 return DeleteAppDomainTimer(handle);
119 private readonly int m_id; // TimerQueues[m_id] == this
120 private AppDomainTimerSafeHandle m_appDomainTimer;
122 private bool m_isAppDomainTimerScheduled;
123 private int m_currentAppDomainTimerStartTicks;
124 private uint m_currentAppDomainTimerDuration;
126 private bool EnsureAppDomainTimerFiresBy(uint requestedDuration)
129 // The VM's timer implementation does not work well for very long-duration timers.
131 // So we'll limit our native timer duration to a "small" value.
132 // This may cause us to attempt to fire timers early, but that's ok -
133 // we'll just see that none of our timers has actually reached its due time,
134 // and schedule the native timer again.
136 const uint maxPossibleDuration = 0x0fffffff;
137 uint actualDuration = Math.Min(requestedDuration, maxPossibleDuration);
139 if (m_isAppDomainTimerScheduled)
141 uint elapsed = (uint)(TickCount - m_currentAppDomainTimerStartTicks);
142 if (elapsed >= m_currentAppDomainTimerDuration)
143 return true; //the timer's about to fire
145 uint remainingDuration = m_currentAppDomainTimerDuration - elapsed;
146 if (actualDuration >= remainingDuration)
147 return true; //the timer will fire earlier than this request
150 // If Pause is underway then do not schedule the timers
151 // A later update during resume will re-schedule
152 if (m_pauseTicks != 0)
154 Debug.Assert(!m_isAppDomainTimerScheduled);
155 Debug.Assert(m_appDomainTimer == null);
159 if (m_appDomainTimer == null || m_appDomainTimer.IsInvalid)
161 Debug.Assert(!m_isAppDomainTimerScheduled);
162 Debug.Assert(m_id >= 0 && m_id < Instances.Length && this == Instances[m_id]);
164 m_appDomainTimer = CreateAppDomainTimer(actualDuration, m_id);
165 if (!m_appDomainTimer.IsInvalid)
167 m_isAppDomainTimerScheduled = true;
168 m_currentAppDomainTimerStartTicks = TickCount;
169 m_currentAppDomainTimerDuration = actualDuration;
179 if (ChangeAppDomainTimer(m_appDomainTimer, actualDuration))
181 m_isAppDomainTimerScheduled = true;
182 m_currentAppDomainTimerStartTicks = TickCount;
183 m_currentAppDomainTimerDuration = actualDuration;
194 // The VM calls this when a native timer fires.
196 internal static void AppDomainTimerCallback(int id)
198 Debug.Assert(id >= 0 && id < Instances.Length && Instances[id].m_id == id);
199 Instances[id].FireNextTimers();
202 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
203 private static extern AppDomainTimerSafeHandle CreateAppDomainTimer(uint dueTime, int id);
205 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
206 private static extern bool ChangeAppDomainTimer(AppDomainTimerSafeHandle handle, uint dueTime);
208 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
209 private static extern bool DeleteAppDomainTimer(IntPtr handle);
213 #region Firing timers
216 // The list of timers
218 private TimerQueueTimer m_timers;
221 private volatile int m_pauseTicks = 0; // Time when Pause was called
225 // Fire any timers that have expired, and update the native timer to schedule the rest of them.
226 // We're in a thread pool work item here, and if there are multiple timers to be fired, we want
227 // to queue all but the first one. The first may can then be invoked synchronously or queued,
228 // a task left up to our caller, which might be firing timers from multiple queues.
230 private void FireNextTimers()
233 // we fire the first timer on this thread; any other timers that might have fired are queued
234 // to the ThreadPool.
236 TimerQueueTimer timerToFireOnThisThread = null;
240 // prevent ThreadAbort while updating state
245 // since we got here, that means our previous timer has fired.
247 m_isAppDomainTimerScheduled = false;
248 bool haveTimerToSchedule = false;
249 uint nextAppDomainTimerDuration = uint.MaxValue;
251 int nowTicks = TickCount;
254 // Sweep through all timers. The ones that have reached their due time
255 // will fire. We will calculate the next native timer due time from the
258 TimerQueueTimer timer = m_timers;
259 while (timer != null)
261 Debug.Assert(timer.m_dueTime != Timeout.UnsignedInfinite);
263 uint elapsed = (uint)(nowTicks - timer.m_startTicks);
264 if (elapsed >= timer.m_dueTime)
267 // Remember the next timer in case we delete this one
269 TimerQueueTimer nextTimer = timer.m_next;
271 if (timer.m_period != Timeout.UnsignedInfinite)
273 timer.m_startTicks = nowTicks;
274 uint elapsedForNextDueTime = elapsed - timer.m_dueTime;
275 if (elapsedForNextDueTime < timer.m_period)
277 // Discount the extra amount of time that has elapsed since the previous firing time to
278 // prevent timer ticks from drifting
279 timer.m_dueTime = timer.m_period - elapsedForNextDueTime;
283 // Enough time has elapsed to fire the timer yet again. The timer is not able to keep up
284 // with the short period, have it fire 1 ms from now to avoid spinning without a delay.
289 // This is a repeating timer; schedule it to run again.
291 if (timer.m_dueTime < nextAppDomainTimerDuration)
293 haveTimerToSchedule = true;
294 nextAppDomainTimerDuration = timer.m_dueTime;
300 // Not repeating; remove it from the queue
306 // If this is the first timer, we'll fire it on this thread. Otherwise, queue it
307 // to the ThreadPool.
309 if (timerToFireOnThisThread == null)
310 timerToFireOnThisThread = timer;
312 QueueTimerCompletion(timer);
319 // This timer hasn't fired yet. Just update the next time the native timer fires.
321 uint remaining = timer.m_dueTime - elapsed;
322 if (remaining < nextAppDomainTimerDuration)
324 haveTimerToSchedule = true;
325 nextAppDomainTimerDuration = remaining;
327 timer = timer.m_next;
331 if (haveTimerToSchedule)
332 EnsureAppDomainTimerFiresBy(nextAppDomainTimerDuration);
337 // Fire the user timer outside of the lock!
339 if (timerToFireOnThisThread != null)
340 timerToFireOnThisThread.Fire();
343 private static void QueueTimerCompletion(TimerQueueTimer timer)
345 // Can use "unsafe" variant because we take care of capturing and restoring the ExecutionContext.
346 ThreadPool.UnsafeQueueCustomWorkItem(timer, forceGlobal: true);
351 #region Queue implementation
353 public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period)
355 if (timer.m_dueTime == Timeout.UnsignedInfinite)
357 // the timer is not in the list; add it (as the head of the list).
358 timer.m_next = m_timers;
360 if (timer.m_next != null)
361 timer.m_next.m_prev = timer;
364 timer.m_dueTime = dueTime;
365 timer.m_period = (period == 0) ? Timeout.UnsignedInfinite : period;
366 timer.m_startTicks = TickCount;
367 return EnsureAppDomainTimerFiresBy(dueTime);
370 public void DeleteTimer(TimerQueueTimer timer)
372 if (timer.m_dueTime != Timeout.UnsignedInfinite)
374 if (timer.m_next != null)
375 timer.m_next.m_prev = timer.m_prev;
376 if (timer.m_prev != null)
377 timer.m_prev.m_next = timer.m_next;
378 if (m_timers == timer)
379 m_timers = timer.m_next;
381 timer.m_dueTime = Timeout.UnsignedInfinite;
382 timer.m_period = Timeout.UnsignedInfinite;
383 timer.m_startTicks = 0;
393 // A timer in our TimerQueue.
395 internal sealed class TimerQueueTimer : IThreadPoolWorkItem
398 // The associated timer queue.
400 private readonly TimerQueue m_associatedTimerQueue;
403 // All fields of this class are protected by a lock on TimerQueue.Instance.
405 // The first four fields are maintained by TimerQueue itself.
407 internal TimerQueueTimer m_next;
408 internal TimerQueueTimer m_prev;
411 // The time, according to TimerQueue.TickCount, when this timer's current interval started.
413 internal int m_startTicks;
416 // Timeout.UnsignedInfinite if we are not going to fire. Otherwise, the offset from m_startTime when we will fire.
418 internal uint m_dueTime;
421 // Timeout.UnsignedInfinite if we are a single-shot timer. Otherwise, the repeat interval.
423 internal uint m_period;
426 // Info about the user's callback
428 private readonly TimerCallback m_timerCallback;
429 private readonly Object m_state;
430 private readonly ExecutionContext m_executionContext;
434 // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only
435 // after all pending callbacks are complete. We set m_canceled to prevent any callbacks that
436 // are already queued from running. We track the number of callbacks currently executing in
437 // m_callbacksRunning. We set m_notifyWhenNoCallbacksRunning only when m_callbacksRunning
440 private int m_callbacksRunning;
441 private volatile bool m_canceled;
442 private volatile WaitHandle m_notifyWhenNoCallbacksRunning;
445 internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period)
447 m_timerCallback = timerCallback;
449 m_dueTime = Timeout.UnsignedInfinite;
450 m_period = Timeout.UnsignedInfinite;
451 m_executionContext = ExecutionContext.Capture();
452 m_associatedTimerQueue = TimerQueue.Instances[Environment.CurrentExecutionId % TimerQueue.Instances.Length];
455 // After the following statement, the timer may fire. No more manipulation of timer state outside of
456 // the lock is permitted beyond this point!
458 if (dueTime != Timeout.UnsignedInfinite)
459 Change(dueTime, period);
462 internal bool Change(uint dueTime, uint period)
466 lock (m_associatedTimerQueue)
469 throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
471 // prevent ThreadAbort while updating state
477 if (dueTime == Timeout.UnsignedInfinite)
479 m_associatedTimerQueue.DeleteTimer(this);
484 if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
485 FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true);
487 success = m_associatedTimerQueue.UpdateTimer(this, dueTime, period);
498 lock (m_associatedTimerQueue)
500 // prevent ThreadAbort while updating state
507 m_associatedTimerQueue.DeleteTimer(this);
514 public bool Close(WaitHandle toSignal)
517 bool shouldSignal = false;
519 lock (m_associatedTimerQueue)
521 // prevent ThreadAbort while updating state
532 m_notifyWhenNoCallbacksRunning = toSignal;
533 m_associatedTimerQueue.DeleteTimer(this);
535 if (m_callbacksRunning == 0)
544 SignalNoCallbacksRunning();
552 bool canceled = false;
554 lock (m_associatedTimerQueue)
556 // prevent ThreadAbort while updating state
560 canceled = m_canceled;
562 m_callbacksRunning++;
571 bool shouldSignal = false;
572 lock (m_associatedTimerQueue)
574 // prevent ThreadAbort while updating state
578 m_callbacksRunning--;
579 if (m_canceled && m_callbacksRunning == 0 && m_notifyWhenNoCallbacksRunning != null)
585 SignalNoCallbacksRunning();
588 void IThreadPoolWorkItem.ExecuteWorkItem() => Fire();
590 void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { }
592 internal void SignalNoCallbacksRunning()
594 Win32Native.SetEvent(m_notifyWhenNoCallbacksRunning.SafeWaitHandle);
597 internal void CallCallback()
599 if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
600 FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty);
602 // call directly if EC flow is suppressed
603 if (m_executionContext == null)
605 m_timerCallback(m_state);
609 ExecutionContext.Run(m_executionContext, s_callCallbackInContext, this);
613 private static readonly ContextCallback s_callCallbackInContext = state =>
615 TimerQueueTimer t = (TimerQueueTimer)state;
616 t.m_timerCallback(t.m_state);
621 // TimerHolder serves as an intermediary between Timer and TimerQueueTimer, releasing the TimerQueueTimer
622 // if the Timer is collected.
623 // This is necessary because Timer itself cannot use its finalizer for this purpose. If it did,
624 // then users could control timer lifetimes using GC.SuppressFinalize/ReRegisterForFinalize.
625 // You might ask, wouldn't that be a good thing? Maybe (though it would be even better to offer this
626 // via first-class APIs), but Timer has never offered this, and adding it now would be a breaking
627 // change, because any code that happened to be suppressing finalization of Timer objects would now
628 // unwittingly be changing the lifetime of those timers.
630 internal sealed class TimerHolder
632 internal TimerQueueTimer m_timer;
634 public TimerHolder(TimerQueueTimer timer)
642 // If shutdown has started, another thread may be suspended while holding the timer lock.
643 // So we can't safely close the timer.
645 // Similarly, we should not close the timer during AD-unload's live-object finalization phase.
646 // A rude abort may have prevented us from releasing the lock.
648 // Note that in either case, the Timer still won't fire, because ThreadPool threads won't be
649 // allowed to run in this AppDomain.
651 if (Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload())
660 GC.SuppressFinalize(this);
663 public bool Close(WaitHandle notifyObject)
665 bool result = m_timer.Close(notifyObject);
666 GC.SuppressFinalize(this);
672 public sealed class Timer : MarshalByRefObject, IDisposable
674 private const UInt32 MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
676 private TimerHolder m_timer;
678 public Timer(TimerCallback callback,
684 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
686 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
688 TimerSetup(callback, state, (UInt32)dueTime, (UInt32)period);
691 public Timer(TimerCallback callback,
696 long dueTm = (long)dueTime.TotalMilliseconds;
698 throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
699 if (dueTm > MAX_SUPPORTED_TIMEOUT)
700 throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge);
702 long periodTm = (long)period.TotalMilliseconds;
704 throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
705 if (periodTm > MAX_SUPPORTED_TIMEOUT)
706 throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge);
708 TimerSetup(callback, state, (UInt32)dueTm, (UInt32)periodTm);
711 [CLSCompliant(false)]
712 public Timer(TimerCallback callback,
717 TimerSetup(callback, state, dueTime, period);
720 public Timer(TimerCallback callback,
726 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
728 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
729 if (dueTime > MAX_SUPPORTED_TIMEOUT)
730 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
731 if (period > MAX_SUPPORTED_TIMEOUT)
732 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
733 TimerSetup(callback, state, (UInt32)dueTime, (UInt32)period);
736 public Timer(TimerCallback callback)
738 int dueTime = -1; // we want timer to be registered, but not activated. Requires caller to call
739 int period = -1; // Change after a timer instance is created. This is to avoid the potential
740 // for a timer to be fired before the returned value is assigned to the variable,
741 // potentially causing the callback to reference a bogus value (if passing the timer to the callback).
743 TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period);
746 private void TimerSetup(TimerCallback callback,
751 if (callback == null)
752 throw new ArgumentNullException(nameof(TimerCallback));
754 m_timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period));
757 public bool Change(int dueTime, int period)
760 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
762 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
764 return m_timer.m_timer.Change((UInt32)dueTime, (UInt32)period);
767 public bool Change(TimeSpan dueTime, TimeSpan period)
769 return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
772 [CLSCompliant(false)]
773 public bool Change(UInt32 dueTime, UInt32 period)
775 return m_timer.m_timer.Change(dueTime, period);
778 public bool Change(long dueTime, long period)
781 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
783 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
784 if (dueTime > MAX_SUPPORTED_TIMEOUT)
785 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
786 if (period > MAX_SUPPORTED_TIMEOUT)
787 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
789 return m_timer.m_timer.Change((UInt32)dueTime, (UInt32)period);
792 public bool Dispose(WaitHandle notifyObject)
794 if (notifyObject == null)
795 throw new ArgumentNullException(nameof(notifyObject));
797 return m_timer.Close(notifyObject);
800 public void Dispose()