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