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.
4 
5 using System.Diagnostics;
6 
7 namespace System.Threading
8 {
TimerCallback(Object state)9     public delegate void TimerCallback(Object state);
10 
11     //
12     // TimerQueue maintains a list of active timers in this AppDomain.  We use a single native timer to schedule
13     // all managed timers in the process.
14     //
15     // Perf assumptions:  We assume that timers are created and destroyed frequently, but rarely actually fire.
16     // There are roughly two types of timer:
17     //
18     //  - timeouts for operations.  These are created and destroyed very frequently, but almost never fire, because
19     //    the whole point is that the timer only fires if something has gone wrong.
20     //
21     //  - scheduled background tasks.  These typically do fire, but they usually have quite long durations.
22     //    So the impact of spending a few extra cycles to fire these is negligible.
23     //
24     // Because of this, we want to choose a data structure with very fast insert and delete times, but we can live
25     // with linear traversal times when firing timers.
26     //
27     // The data structure we've chosen is an unordered doubly-linked list of active timers.  This gives O(1) insertion
28     // and removal, and O(N) traversal when finding expired timers.
29     //
30     // Note that all instance methods of this class require that the caller hold a lock on TimerQueue.Instance.
31     //
32     internal partial class TimerQueue
33     {
34         #region singleton pattern implementation
35 
36         // The one-and-only TimerQueue for the AppDomain.
37         private static TimerQueue s_queue = new TimerQueue();
38 
39         public static TimerQueue Instance
40         {
41             get { return s_queue; }
42         }
43 
TimerQueue()44         private TimerQueue()
45         {
46             // empty private constructor to ensure we remain a singleton.
47         }
48 
49         #endregion
50 
51         #region interface to native per-AppDomain timer
52 
53         private int _currentNativeTimerStartTicks;
54         private uint _currentNativeTimerDuration = UInt32.MaxValue;
55 
EnsureAppDomainTimerFiresBy(uint requestedDuration)56         private void EnsureAppDomainTimerFiresBy(uint requestedDuration)
57         {
58             //
59             // The CLR VM's timer implementation does not work well for very long-duration timers.
60             // See kb 950807.
61             // So we'll limit our native timer duration to a "small" value.
62             // This may cause us to attempt to fire timers early, but that's ok -
63             // we'll just see that none of our timers has actually reached its due time,
64             // and schedule the native timer again.
65             //
66             const uint maxPossibleDuration = 0x0fffffff;
67             uint actualDuration = Math.Min(requestedDuration, maxPossibleDuration);
68 
69             if (_currentNativeTimerDuration != UInt32.MaxValue)
70             {
71                 uint elapsed = (uint)(TickCount - _currentNativeTimerStartTicks);
72                 if (elapsed >= _currentNativeTimerDuration)
73                     return; //the timer's about to fire
74 
75                 uint remainingDuration = _currentNativeTimerDuration - elapsed;
76                 if (actualDuration >= remainingDuration)
77                     return; //the timer will fire earlier than this request
78             }
79 
80             SetTimer(actualDuration);
81             _currentNativeTimerDuration = actualDuration;
82 
83             _currentNativeTimerStartTicks = TickCount;
84         }
85 
86         #endregion
87 
88         #region Firing timers
89 
90         //
91         // The list of timers
92         //
93         private TimerQueueTimer _timers;
94         readonly internal Lock Lock = new Lock();
95 
96         //
97         // Fire any timers that have expired, and update the native timer to schedule the rest of them.
98         //
FireNextTimers()99         private void FireNextTimers()
100         {
101             //
102             // we fire the first timer on this thread; any other timers that might have fired are queued
103             // to the ThreadPool.
104             //
105             TimerQueueTimer timerToFireOnThisThread = null;
106 
107             using (LockHolder.Hold(Lock))
108             {
109                 //
110                 // since we got here, that means our previous timer has fired.
111                 //
112                 _currentNativeTimerDuration = UInt32.MaxValue;
113 
114                 bool haveTimerToSchedule = false;
115                 uint nextAppDomainTimerDuration = uint.MaxValue;
116 
117                 int nowTicks = TickCount;
118 
119                 //
120                 // Sweep through all timers.  The ones that have reached their due time
121                 // will fire.  We will calculate the next native timer due time from the
122                 // other timers.
123                 //
124                 TimerQueueTimer timer = _timers;
125                 while (timer != null)
126                 {
127                     Debug.Assert(timer.m_dueTime != Timeout.UnsignedInfinite);
128 
129                     uint elapsed = (uint)(nowTicks - timer.m_startTicks);
130                     if (elapsed >= timer.m_dueTime)
131                     {
132                         //
133                         // Remember the next timer in case we delete this one
134                         //
135                         TimerQueueTimer nextTimer = timer.m_next;
136 
137                         if (timer.m_period != Timeout.UnsignedInfinite)
138                         {
139                             timer.m_startTicks = nowTicks;
140                             uint elapsedForNextDueTime = elapsed - timer.m_dueTime;
141                             if (elapsedForNextDueTime < timer.m_period)
142                             {
143                                 // Discount the extra time that has elapsed since the previous firing
144                                 // to prevent the timer ticks from drifting
145                                 timer.m_dueTime = timer.m_period - elapsedForNextDueTime;
146                             }
147                             else
148                             {
149                                 // Enough time has elapsed to fire the timer yet again. The timer is not able to keep up
150                                 // with the short period, have it fire 1 ms from now to avoid spnning without delay.
151                                 timer.m_dueTime = 1;
152                             }
153 
154                             //
155                             // This is a repeating timer; schedule it to run again.
156                             //
157                             if (timer.m_dueTime < nextAppDomainTimerDuration)
158                             {
159                                 haveTimerToSchedule = true;
160                                 nextAppDomainTimerDuration = timer.m_dueTime;
161                             }
162                         }
163                         else
164                         {
165                             //
166                             // Not repeating; remove it from the queue
167                             //
168                             DeleteTimer(timer);
169                         }
170 
171                         //
172                         // If this is the first timer, we'll fire it on this thread.  Otherwise, queue it
173                         // to the ThreadPool.
174                         //
175                         if (timerToFireOnThisThread == null)
176                             timerToFireOnThisThread = timer;
177                         else
178                             QueueTimerCompletion(timer);
179 
180                         timer = nextTimer;
181                     }
182                     else
183                     {
184                         //
185                         // This timer hasn't fired yet.  Just update the next time the native timer fires.
186                         //
187                         uint remaining = timer.m_dueTime - elapsed;
188                         if (remaining < nextAppDomainTimerDuration)
189                         {
190                             haveTimerToSchedule = true;
191                             nextAppDomainTimerDuration = remaining;
192                         }
193                         timer = timer.m_next;
194                     }
195                 }
196 
197                 if (haveTimerToSchedule)
198                     EnsureAppDomainTimerFiresBy(nextAppDomainTimerDuration);
199             }
200 
201             //
202             // Fire the user timer outside of the lock!
203             //
204             if (timerToFireOnThisThread != null)
205                 timerToFireOnThisThread.Fire();
206         }
207 
QueueTimerCompletion(TimerQueueTimer timer)208         private static void QueueTimerCompletion(TimerQueueTimer timer)
209         {
210             WaitCallback callback = s_fireQueuedTimerCompletion;
211             if (callback == null)
212                 s_fireQueuedTimerCompletion = callback = new WaitCallback(FireQueuedTimerCompletion);
213 
214             // Can use "unsafe" variant because we take care of capturing and restoring
215             // the ExecutionContext.
216             ThreadPool.UnsafeQueueUserWorkItem(callback, timer);
217         }
218 
219         private static WaitCallback s_fireQueuedTimerCompletion;
220 
FireQueuedTimerCompletion(object state)221         private static void FireQueuedTimerCompletion(object state)
222         {
223             ((TimerQueueTimer)state).Fire();
224         }
225 
226         #endregion
227 
228         #region Queue implementation
229 
UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period)230         public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period)
231         {
232             if (timer.m_dueTime == Timeout.UnsignedInfinite)
233             {
234                 // the timer is not in the list; add it (as the head of the list).
235                 timer.m_next = _timers;
236                 timer.m_prev = null;
237                 if (timer.m_next != null)
238                     timer.m_next.m_prev = timer;
239                 _timers = timer;
240             }
241             timer.m_dueTime = dueTime;
242             timer.m_period = (period == 0) ? Timeout.UnsignedInfinite : period;
243             timer.m_startTicks = TickCount;
244             EnsureAppDomainTimerFiresBy(dueTime);
245             return true;
246         }
247 
DeleteTimer(TimerQueueTimer timer)248         public void DeleteTimer(TimerQueueTimer timer)
249         {
250             if (timer.m_dueTime != Timeout.UnsignedInfinite)
251             {
252                 if (timer.m_next != null)
253                     timer.m_next.m_prev = timer.m_prev;
254                 if (timer.m_prev != null)
255                     timer.m_prev.m_next = timer.m_next;
256                 if (_timers == timer)
257                     _timers = timer.m_next;
258 
259                 timer.m_dueTime = Timeout.UnsignedInfinite;
260                 timer.m_period = Timeout.UnsignedInfinite;
261                 timer.m_startTicks = 0;
262                 timer.m_prev = null;
263                 timer.m_next = null;
264             }
265         }
266         #endregion
267     }
268 
269     //
270     // A timer in our TimerQueue.
271     //
272     internal sealed partial class TimerQueueTimer
273     {
274         //
275         // All fields of this class are protected by a lock on TimerQueue.Instance.
276         //
277         // The first four fields are maintained by TimerQueue itself.
278         //
279         internal TimerQueueTimer m_next;
280         internal TimerQueueTimer m_prev;
281 
282         //
283         // The time, according to TimerQueue.TickCount, when this timer's current interval started.
284         //
285         internal int m_startTicks;
286 
287         //
288         // Timeout.UnsignedInfinite if we are not going to fire.  Otherwise, the offset from m_startTime when we will fire.
289         //
290         internal uint m_dueTime;
291 
292         //
293         // Timeout.UnsignedInfinite if we are a single-shot timer.  Otherwise, the repeat interval.
294         //
295         internal uint m_period;
296 
297         //
298         // Info about the user's callback
299         //
300         private readonly TimerCallback _timerCallback;
301         private readonly Object _state;
302         private readonly ExecutionContext _executionContext;
303 
304 
305         //
306         // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only
307         // after all pending callbacks are complete.  We set _canceled to prevent any callbacks that
308         // are already queued from running.  We track the number of callbacks currently executing in
309         // _callbacksRunning.  We set _notifyWhenNoCallbacksRunning only when _callbacksRunning
310         // reaches zero.
311         //
312         private int _callbacksRunning;
313         private volatile bool _canceled;
314         private volatile WaitHandle _notifyWhenNoCallbacksRunning;
315 
316 
TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period)317         internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period)
318         {
319             _timerCallback = timerCallback;
320             _state = state;
321             m_dueTime = Timeout.UnsignedInfinite;
322             m_period = Timeout.UnsignedInfinite;
323             _executionContext = ExecutionContext.Capture();
324 
325             //
326             // After the following statement, the timer may fire.  No more manipulation of timer state outside of
327             // the lock is permitted beyond this point!
328             //
329             if (dueTime != Timeout.UnsignedInfinite)
330                 Change(dueTime, period);
331         }
332 
333 
Change(uint dueTime, uint period)334         internal bool Change(uint dueTime, uint period)
335         {
336             bool success;
337 
338             using (LockHolder.Hold(TimerQueue.Instance.Lock))
339             {
340                 if (_canceled)
341                     throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
342 
343                 m_period = period;
344 
345                 if (dueTime == Timeout.UnsignedInfinite)
346                 {
347                     TimerQueue.Instance.DeleteTimer(this);
348                     success = true;
349                 }
350                 else
351                 {
352                     success = TimerQueue.Instance.UpdateTimer(this, dueTime, period);
353                 }
354             }
355 
356             return success;
357         }
358 
359 
Close()360         public void Close()
361         {
362             using (LockHolder.Hold(TimerQueue.Instance.Lock))
363             {
364                 if (!_canceled)
365                 {
366                     _canceled = true;
367                     TimerQueue.Instance.DeleteTimer(this);
368                 }
369             }
370         }
371 
372 
Close(WaitHandle toSignal)373         public bool Close(WaitHandle toSignal)
374         {
375             bool success;
376             bool shouldSignal = false;
377 
378             using (LockHolder.Hold(TimerQueue.Instance.Lock))
379             {
380                 if (_canceled)
381                 {
382                     success = false;
383                 }
384                 else
385                 {
386                     _canceled = true;
387                     _notifyWhenNoCallbacksRunning = toSignal;
388                     TimerQueue.Instance.DeleteTimer(this);
389 
390                     if (_callbacksRunning == 0)
391                         shouldSignal = true;
392 
393                     success = true;
394                 }
395             }
396 
397             if (shouldSignal)
398                 SignalNoCallbacksRunning();
399 
400             return success;
401         }
402 
403 
Fire()404         internal void Fire()
405         {
406             bool canceled = false;
407 
408             lock (TimerQueue.Instance)
409             {
410                 canceled = _canceled;
411                 if (!canceled)
412                     _callbacksRunning++;
413             }
414 
415             if (canceled)
416                 return;
417 
418             CallCallback();
419 
420             bool shouldSignal = false;
421             using (LockHolder.Hold(TimerQueue.Instance.Lock))
422             {
423                 _callbacksRunning--;
424                 if (_canceled && _callbacksRunning == 0 && _notifyWhenNoCallbacksRunning != null)
425                     shouldSignal = true;
426             }
427 
428             if (shouldSignal)
429                 SignalNoCallbacksRunning();
430         }
431 
CallCallback()432         internal void CallCallback()
433         {
434             ContextCallback callback = s_callCallbackInContext;
435             if (callback == null)
436                 s_callCallbackInContext = callback = new ContextCallback(CallCallbackInContext);
437 
438             // call directly if EC flow is suppressed
439             if (_executionContext == null)
440             {
441                 _timerCallback(_state);
442             }
443             else
444             {
445                 ExecutionContext.Run(_executionContext, callback, this);
446             }
447         }
448 
449         private static ContextCallback s_callCallbackInContext;
450 
CallCallbackInContext(object state)451         private static void CallCallbackInContext(object state)
452         {
453             TimerQueueTimer t = (TimerQueueTimer)state;
454             t._timerCallback(t._state);
455         }
456     }
457 
458     //
459     // TimerHolder serves as an intermediary between Timer and TimerQueueTimer, releasing the TimerQueueTimer
460     // if the Timer is collected.
461     // This is necessary because Timer itself cannot use its finalizer for this purpose.  If it did,
462     // then users could control timer lifetimes using GC.SuppressFinalize/ReRegisterForFinalize.
463     // You might ask, wouldn't that be a good thing?  Maybe (though it would be even better to offer this
464     // via first-class APIs), but Timer has never offered this, and adding it now would be a breaking
465     // change, because any code that happened to be suppressing finalization of Timer objects would now
466     // unwittingly be changing the lifetime of those timers.
467     //
468     internal sealed class TimerHolder
469     {
470         internal TimerQueueTimer m_timer;
471 
TimerHolder(TimerQueueTimer timer)472         public TimerHolder(TimerQueueTimer timer)
473         {
474             m_timer = timer;
475         }
476 
~TimerHolder()477         ~TimerHolder()
478         {
479             m_timer.Close();
480         }
481 
Close()482         public void Close()
483         {
484             m_timer.Close();
485             GC.SuppressFinalize(this);
486         }
487 
Close(WaitHandle notifyObject)488         public bool Close(WaitHandle notifyObject)
489         {
490             bool result = m_timer.Close(notifyObject);
491             GC.SuppressFinalize(this);
492             return result;
493         }
494     }
495 
496 
497     public sealed class Timer : MarshalByRefObject, IDisposable
498     {
499         private const UInt32 MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
500 
501         private TimerHolder _timer;
502 
Timer(TimerCallback callback, Object state, int dueTime, int period)503         public Timer(TimerCallback callback,
504                      Object state,
505                      int dueTime,
506                      int period)
507         {
508             if (dueTime < -1)
509                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
510             if (period < -1)
511                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
512 
513             TimerSetup(callback, state, (UInt32)dueTime, (UInt32)period);
514         }
515 
Timer(TimerCallback callback, Object state, TimeSpan dueTime, TimeSpan period)516         public Timer(TimerCallback callback,
517                      Object state,
518                      TimeSpan dueTime,
519                      TimeSpan period)
520         {
521             long dueTm = (long)dueTime.TotalMilliseconds;
522             if (dueTm < -1)
523                 throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
524             if (dueTm > MAX_SUPPORTED_TIMEOUT)
525                 throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge);
526 
527             long periodTm = (long)period.TotalMilliseconds;
528             if (periodTm < -1)
529                 throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
530             if (periodTm > MAX_SUPPORTED_TIMEOUT)
531                 throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge);
532 
533             TimerSetup(callback, state, (UInt32)dueTm, (UInt32)periodTm);
534         }
535 
536         [CLSCompliant(false)]
Timer(TimerCallback callback, Object state, UInt32 dueTime, UInt32 period)537         public Timer(TimerCallback callback,
538                      Object state,
539                      UInt32 dueTime,
540                      UInt32 period)
541         {
542             TimerSetup(callback, state, dueTime, period);
543         }
544 
Timer(TimerCallback callback, Object state, long dueTime, long period)545         public Timer(TimerCallback callback,
546                      Object state,
547                      long dueTime,
548                      long period)
549         {
550             if (dueTime < -1)
551                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
552             if (period < -1)
553                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
554             if (dueTime > MAX_SUPPORTED_TIMEOUT)
555                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
556             if (period > MAX_SUPPORTED_TIMEOUT)
557                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
558             TimerSetup(callback, state, (UInt32)dueTime, (UInt32)period);
559         }
560 
Timer(TimerCallback callback)561         public Timer(TimerCallback callback)
562         {
563             int dueTime = -1;    // we want timer to be registered, but not activated.  Requires caller to call
564             int period = -1;    // Change after a timer instance is created.  This is to avoid the potential
565                                 // for a timer to be fired before the returned value is assigned to the variable,
566                                 // potentially causing the callback to reference a bogus value (if passing the timer to the callback).
567 
568             TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period);
569         }
570 
TimerSetup(TimerCallback callback, Object state, UInt32 dueTime, UInt32 period)571         private void TimerSetup(TimerCallback callback,
572                                 Object state,
573                                 UInt32 dueTime,
574                                 UInt32 period)
575         {
576             if (callback == null)
577                 throw new ArgumentNullException(nameof(TimerCallback));
578 
579             _timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period));
580         }
581 
Change(int dueTime, int period)582         public bool Change(int dueTime, int period)
583         {
584             if (dueTime < -1)
585                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
586             if (period < -1)
587                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
588 
589             return _timer.m_timer.Change((UInt32)dueTime, (UInt32)period);
590         }
591 
Change(TimeSpan dueTime, TimeSpan period)592         public bool Change(TimeSpan dueTime, TimeSpan period)
593         {
594             return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
595         }
596 
597         [CLSCompliant(false)]
Change(UInt32 dueTime, UInt32 period)598         public bool Change(UInt32 dueTime, UInt32 period)
599         {
600             return _timer.m_timer.Change(dueTime, period);
601         }
602 
Change(long dueTime, long period)603         public bool Change(long dueTime, long period)
604         {
605             if (dueTime < -1)
606                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
607             if (period < -1)
608                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
609             if (dueTime > MAX_SUPPORTED_TIMEOUT)
610                 throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
611             if (period > MAX_SUPPORTED_TIMEOUT)
612                 throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
613 
614             return _timer.m_timer.Change((UInt32)dueTime, (UInt32)period);
615         }
616 
Dispose(WaitHandle notifyObject)617         public bool Dispose(WaitHandle notifyObject)
618         {
619             if (notifyObject == null)
620                 throw new ArgumentNullException(nameof(notifyObject));
621 
622             return _timer.Close(notifyObject);
623         }
624 
Dispose()625         public void Dispose()
626         {
627             _timer.Close();
628         }
629 
KeepRootedWhileScheduled()630         internal void KeepRootedWhileScheduled()
631         {
632             GC.SuppressFinalize(_timer);
633         }
634     }
635 }
636