Timer.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  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. using Internal.Runtime.Augments;
  5. using System.Diagnostics;
  6. using System.Diagnostics.Tracing;
  7. using System.Threading.Tasks;
  8. using Thread = Internal.Runtime.Augments.RuntimeThread;
  9. namespace System.Threading
  10. {
  11. public delegate void TimerCallback(object state);
  12. // TimerQueue maintains a list of active timers. We use a single native timer to schedule all managed timers
  13. // 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, and we can live
  25. // with linear traversal times when firing timers. However, we still want to minimize the number of timers
  26. // we need to traverse while doing the linear walk: in cases where we have lots of long-lived timers as well as
  27. // lots of short-lived timers, when the short-lived timers fire, they incur the cost of walking the long-lived ones.
  28. //
  29. // The data structure we've chosen is an unordered doubly-linked list of active timers. This gives O(1) insertion
  30. // and removal, and O(N) traversal when finding expired timers. We maintain two such lists: one for all of the
  31. // timers that'll next fire within a certain threshold, and one for the rest.
  32. //
  33. // Note that all instance methods of this class require that the caller hold a lock on the TimerQueue instance.
  34. // We partition the timers across multiple TimerQueues, each with its own lock and set of short/long lists,
  35. // in order to minimize contention when lots of threads are concurrently creating and destroying timers often.
  36. internal partial class TimerQueue
  37. {
  38. #region Shared TimerQueue instances
  39. public static TimerQueue[] Instances { get; } = CreateTimerQueues();
  40. private static TimerQueue[] CreateTimerQueues()
  41. {
  42. var queues = new TimerQueue[Environment.ProcessorCount];
  43. for (int i = 0; i < queues.Length; i++)
  44. {
  45. queues[i] = new TimerQueue(i);
  46. }
  47. return queues;
  48. }
  49. #endregion
  50. #region interface to native timer
  51. private bool _isTimerScheduled;
  52. private int _currentTimerStartTicks;
  53. private uint _currentTimerDuration;
  54. private bool EnsureTimerFiresBy(uint requestedDuration)
  55. {
  56. // The VM's timer implementation does not work well for very long-duration timers.
  57. // See kb 950807.
  58. // So we'll limit our native timer duration to a "small" value.
  59. // This may cause us to attempt to fire timers early, but that's ok -
  60. // we'll just see that none of our timers has actually reached its due time,
  61. // and schedule the native timer again.
  62. const uint maxPossibleDuration = 0x0fffffff;
  63. uint actualDuration = Math.Min(requestedDuration, maxPossibleDuration);
  64. if (_isTimerScheduled)
  65. {
  66. uint elapsed = (uint)(TickCount - _currentTimerStartTicks);
  67. if (elapsed >= _currentTimerDuration)
  68. return true; //the timer's about to fire
  69. uint remainingDuration = _currentTimerDuration - elapsed;
  70. if (actualDuration >= remainingDuration)
  71. return true; //the timer will fire earlier than this request
  72. }
  73. // If Pause is underway then do not schedule the timers
  74. // A later update during resume will re-schedule
  75. if (_pauseTicks != 0)
  76. {
  77. Debug.Assert(!_isTimerScheduled);
  78. return true;
  79. }
  80. if (SetTimer(actualDuration))
  81. {
  82. _isTimerScheduled = true;
  83. _currentTimerStartTicks = TickCount;
  84. _currentTimerDuration = actualDuration;
  85. return true;
  86. }
  87. return false;
  88. }
  89. #endregion
  90. #region Firing timers
  91. // The two lists of timers that are part of this TimerQueue. They conform to a single guarantee:
  92. // no timer in _longTimers has an absolute next firing time <= _currentAbsoluteThreshold.
  93. // That way, when FireNextTimers is invoked, we always process the short list, and we then only
  94. // process the long list if the current time is greater than _currentAbsoluteThreshold (or
  95. // if the short list is now empty and we need to process the long list to know when to next
  96. // invoke FireNextTimers).
  97. private TimerQueueTimer _shortTimers;
  98. private TimerQueueTimer _longTimers;
  99. // The current threshold, an absolute time where any timers scheduled to go off at or
  100. // before this time must be queued to the short list.
  101. private int _currentAbsoluteThreshold = ShortTimersThresholdMilliseconds;
  102. // Default threshold that separates which timers target _shortTimers vs _longTimers. The threshold
  103. // is chosen to balance the number of timers in the small list against the frequency with which
  104. // we need to scan the long list. It's thus somewhat arbitrary and could be changed based on
  105. // observed workload demand. The larger the number, the more timers we'll likely need to enumerate
  106. // every time the timer fires, but also the more likely it is that when it does we won't
  107. // need to look at the long list because the current time will be <= _currentAbsoluteThreshold.
  108. private const int ShortTimersThresholdMilliseconds = 333;
  109. // Time when Pause was called
  110. private volatile int _pauseTicks = 0;
  111. // Fire any timers that have expired, and update the native timer to schedule the rest of them.
  112. // We're in a thread pool work item here, and if there are multiple timers to be fired, we want
  113. // to queue all but the first one. The first may can then be invoked synchronously or queued,
  114. // a task left up to our caller, which might be firing timers from multiple queues.
  115. private void FireNextTimers()
  116. {
  117. // We fire the first timer on this thread; any other timers that need to be fired
  118. // are queued to the ThreadPool.
  119. TimerQueueTimer timerToFireOnThisThread = null;
  120. lock (this)
  121. {
  122. // Since we got here, that means our previous timer has fired.
  123. _isTimerScheduled = false;
  124. bool haveTimerToSchedule = false;
  125. uint nextTimerDuration = uint.MaxValue;
  126. int nowTicks = TickCount;
  127. // Sweep through the "short" timers. If the current tick count is greater than
  128. // the current threshold, also sweep through the "long" timers. Finally, as part
  129. // of sweeping the long timers, move anything that'll fire within the next threshold
  130. // to the short list. It's functionally ok if more timers end up in the short list
  131. // than is truly necessary (but not the opposite).
  132. TimerQueueTimer timer = _shortTimers;
  133. for (int listNum = 0; listNum < 2; listNum++) // short == 0, long == 1
  134. {
  135. while (timer != null)
  136. {
  137. Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "A timer in the list must have a valid due time.");
  138. // Save off the next timer to examine, in case our examination of this timer results
  139. // in our deleting or moving it; we'll continue after with this saved next timer.
  140. TimerQueueTimer next = timer._next;
  141. uint elapsed = (uint)(nowTicks - timer._startTicks);
  142. int remaining = (int)timer._dueTime - (int)elapsed;
  143. if (remaining <= 0)
  144. {
  145. // Timer is ready to fire.
  146. if (timer._period != Timeout.UnsignedInfinite)
  147. {
  148. // This is a repeating timer; schedule it to run again.
  149. // Discount the extra amount of time that has elapsed since the previous firing time to
  150. // prevent timer ticks from drifting. If enough time has already elapsed for the timer to fire
  151. // again, meaning the timer can't keep up with the short period, have it fire 1 ms from now to
  152. // avoid spinning without a delay.
  153. timer._startTicks = nowTicks;
  154. uint elapsedForNextDueTime = elapsed - timer._dueTime;
  155. timer._dueTime = (elapsedForNextDueTime < timer._period) ?
  156. timer._period - elapsedForNextDueTime :
  157. 1;
  158. // Update the timer if this becomes the next timer to fire.
  159. if (timer._dueTime < nextTimerDuration)
  160. {
  161. haveTimerToSchedule = true;
  162. nextTimerDuration = timer._dueTime;
  163. }
  164. // Validate that the repeating timer is still on the right list. It's likely that
  165. // it started in the long list and was moved to the short list at some point, so
  166. // we now want to move it back to the long list if that's where it belongs. Note that
  167. // if we're currently processing the short list and move it to the long list, we may
  168. // end up revisiting it again if we also enumerate the long list, but we will have already
  169. // updated the due time appropriately so that we won't fire it again (it's also possible
  170. // but rare that we could be moving a timer from the long list to the short list here,
  171. // if the initial due time was set to be long but the timer then had a short period).
  172. bool targetShortList = (nowTicks + timer._dueTime) - _currentAbsoluteThreshold <= 0;
  173. if (timer._short != targetShortList)
  174. {
  175. MoveTimerToCorrectList(timer, targetShortList);
  176. }
  177. }
  178. else
  179. {
  180. // Not repeating; remove it from the queue
  181. DeleteTimer(timer);
  182. }
  183. // If this is the first timer, we'll fire it on this thread (after processing
  184. // all others). Otherwise, queue it to the ThreadPool.
  185. if (timerToFireOnThisThread == null)
  186. {
  187. timerToFireOnThisThread = timer;
  188. }
  189. else
  190. {
  191. ThreadPool.UnsafeQueueUserWorkItemInternal(timer, preferLocal: false);
  192. }
  193. }
  194. else
  195. {
  196. // This timer isn't ready to fire. Update the next time the native timer fires if necessary,
  197. // and move this timer to the short list if its remaining time is now at or under the threshold.
  198. if (remaining < nextTimerDuration)
  199. {
  200. haveTimerToSchedule = true;
  201. nextTimerDuration = (uint)remaining;
  202. }
  203. if (!timer._short && remaining <= ShortTimersThresholdMilliseconds)
  204. {
  205. MoveTimerToCorrectList(timer, shortList: true);
  206. }
  207. }
  208. timer = next;
  209. }
  210. // Switch to process the long list if necessary.
  211. if (listNum == 0)
  212. {
  213. // Determine how much time remains between now and the current threshold. If time remains,
  214. // we can skip processing the long list. We use > rather than >= because, although we
  215. // know that if remaining == 0 no timers in the long list will need to be fired, we
  216. // don't know without looking at them when we'll need to call FireNextTimers again. We
  217. // could in that case just set the next firing to 1, but we may as well just iterate the
  218. // long list now; otherwise, most timers created in the interim would end up in the long
  219. // list and we'd likely end up paying for another invocation of FireNextTimers that could
  220. // have been delayed longer (to whatever is the current minimum in the long list).
  221. int remaining = _currentAbsoluteThreshold - nowTicks;
  222. if (remaining > 0)
  223. {
  224. if (_shortTimers == null && _longTimers != null)
  225. {
  226. // We don't have any short timers left and we haven't examined the long list,
  227. // which means we likely don't have an accurate nextTimerDuration.
  228. // But we do know that nothing in the long list will be firing before or at _currentAbsoluteThreshold,
  229. // so we can just set nextTimerDuration to the difference between then and now.
  230. nextTimerDuration = (uint)remaining + 1;
  231. haveTimerToSchedule = true;
  232. }
  233. break;
  234. }
  235. // Switch to processing the long list.
  236. timer = _longTimers;
  237. // Now that we're going to process the long list, update the current threshold.
  238. _currentAbsoluteThreshold = nowTicks + ShortTimersThresholdMilliseconds;
  239. }
  240. }
  241. // If we still have scheduled timers, update the timer to ensure it fires
  242. // in time for the next one in line.
  243. if (haveTimerToSchedule)
  244. {
  245. EnsureTimerFiresBy(nextTimerDuration);
  246. }
  247. }
  248. // Fire the user timer outside of the lock!
  249. timerToFireOnThisThread?.Fire();
  250. }
  251. #endregion
  252. #region Queue implementation
  253. public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period)
  254. {
  255. int nowTicks = TickCount;
  256. // The timer can be put onto the short list if it's next absolute firing time
  257. // is <= the current absolute threshold.
  258. int absoluteDueTime = (int)(nowTicks + dueTime);
  259. bool shouldBeShort = _currentAbsoluteThreshold - absoluteDueTime >= 0;
  260. if (timer._dueTime == Timeout.UnsignedInfinite)
  261. {
  262. // If the timer wasn't previously scheduled, now add it to the right list.
  263. timer._short = shouldBeShort;
  264. LinkTimer(timer);
  265. }
  266. else if (timer._short != shouldBeShort)
  267. {
  268. // If the timer was previously scheduled, but this update should cause
  269. // it to move over the list threshold in either direction, do so.
  270. UnlinkTimer(timer);
  271. timer._short = shouldBeShort;
  272. LinkTimer(timer);
  273. }
  274. timer._dueTime = dueTime;
  275. timer._period = (period == 0) ? Timeout.UnsignedInfinite : period;
  276. timer._startTicks = nowTicks;
  277. return EnsureTimerFiresBy(dueTime);
  278. }
  279. public void MoveTimerToCorrectList(TimerQueueTimer timer, bool shortList)
  280. {
  281. Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "Expected timer to be on a list.");
  282. Debug.Assert(timer._short != shortList, "Unnecessary if timer is already on the right list.");
  283. // Unlink it from whatever list it's on, change its list association, then re-link it.
  284. UnlinkTimer(timer);
  285. timer._short = shortList;
  286. LinkTimer(timer);
  287. }
  288. private void LinkTimer(TimerQueueTimer timer)
  289. {
  290. // Use timer._short to decide to which list to add.
  291. ref TimerQueueTimer listHead = ref timer._short ? ref _shortTimers : ref _longTimers;
  292. timer._next = listHead;
  293. if (timer._next != null)
  294. {
  295. timer._next._prev = timer;
  296. }
  297. timer._prev = null;
  298. listHead = timer;
  299. }
  300. private void UnlinkTimer(TimerQueueTimer timer)
  301. {
  302. TimerQueueTimer t = timer._next;
  303. if (t != null)
  304. {
  305. t._prev = timer._prev;
  306. }
  307. if (_shortTimers == timer)
  308. {
  309. Debug.Assert(timer._short);
  310. _shortTimers = t;
  311. }
  312. else if (_longTimers == timer)
  313. {
  314. Debug.Assert(!timer._short);
  315. _longTimers = t;
  316. }
  317. t = timer._prev;
  318. if (t != null)
  319. {
  320. t._next = timer._next;
  321. }
  322. // At this point the timer is no longer in a list, but its next and prev
  323. // references may still point to other nodes. UnlinkTimer should thus be
  324. // followed by something that overwrites those references, either with null
  325. // if deleting the timer or other nodes if adding it to another list.
  326. }
  327. public void DeleteTimer(TimerQueueTimer timer)
  328. {
  329. if (timer._dueTime != Timeout.UnsignedInfinite)
  330. {
  331. UnlinkTimer(timer);
  332. timer._prev = null;
  333. timer._next = null;
  334. timer._dueTime = Timeout.UnsignedInfinite;
  335. timer._period = Timeout.UnsignedInfinite;
  336. timer._startTicks = 0;
  337. timer._short = false;
  338. }
  339. }
  340. #endregion
  341. }
  342. // A timer in our TimerQueue.
  343. internal sealed partial class TimerQueueTimer : IThreadPoolWorkItem
  344. {
  345. // The associated timer queue.
  346. private readonly TimerQueue _associatedTimerQueue;
  347. // All mutable fields of this class are protected by a lock on _associatedTimerQueue.
  348. // The first six fields are maintained by TimerQueue.
  349. // Links to the next and prev timers in the list.
  350. internal TimerQueueTimer _next;
  351. internal TimerQueueTimer _prev;
  352. // true if on the short list; otherwise, false.
  353. internal bool _short;
  354. // The time, according to TimerQueue.TickCount, when this timer's current interval started.
  355. internal int _startTicks;
  356. // Timeout.UnsignedInfinite if we are not going to fire. Otherwise, the offset from _startTime when we will fire.
  357. internal uint _dueTime;
  358. // Timeout.UnsignedInfinite if we are a single-shot timer. Otherwise, the repeat interval.
  359. internal uint _period;
  360. // Info about the user's callback
  361. private readonly TimerCallback _timerCallback;
  362. private readonly object _state;
  363. private readonly ExecutionContext _executionContext;
  364. // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only
  365. // after all pending callbacks are complete. We set _canceled to prevent any callbacks that
  366. // are already queued from running. We track the number of callbacks currently executing in
  367. // _callbacksRunning. We set _notifyWhenNoCallbacksRunning only when _callbacksRunning
  368. // reaches zero. Same applies if Timer.DisposeAsync() is used, except with a Task<bool>
  369. // instead of with a provided WaitHandle.
  370. private int _callbacksRunning;
  371. private volatile bool _canceled;
  372. private volatile object _notifyWhenNoCallbacksRunning; // may be either WaitHandle or Task<bool>
  373. internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period, bool flowExecutionContext)
  374. {
  375. _timerCallback = timerCallback;
  376. _state = state;
  377. _dueTime = Timeout.UnsignedInfinite;
  378. _period = Timeout.UnsignedInfinite;
  379. if (flowExecutionContext)
  380. {
  381. _executionContext = ExecutionContext.Capture();
  382. }
  383. _associatedTimerQueue = TimerQueue.Instances[RuntimeThread.GetCurrentProcessorId() % TimerQueue.Instances.Length];
  384. // After the following statement, the timer may fire. No more manipulation of timer state outside of
  385. // the lock is permitted beyond this point!
  386. if (dueTime != Timeout.UnsignedInfinite)
  387. Change(dueTime, period);
  388. }
  389. internal bool Change(uint dueTime, uint period)
  390. {
  391. bool success;
  392. lock (_associatedTimerQueue)
  393. {
  394. if (_canceled)
  395. throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
  396. _period = period;
  397. if (dueTime == Timeout.UnsignedInfinite)
  398. {
  399. _associatedTimerQueue.DeleteTimer(this);
  400. success = true;
  401. }
  402. else
  403. {
  404. if (
  405. #if CORECLR
  406. // Don't emit this event during EventPipeController. This avoids initializing FrameworkEventSource during start-up which is expensive relative to the rest of start-up.
  407. !EventPipeController.Initializing &&
  408. #endif
  409. FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
  410. FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true, (int)dueTime, (int)period);
  411. success = _associatedTimerQueue.UpdateTimer(this, dueTime, period);
  412. }
  413. }
  414. return success;
  415. }
  416. public void Close()
  417. {
  418. lock (_associatedTimerQueue)
  419. {
  420. if (!_canceled)
  421. {
  422. _canceled = true;
  423. _associatedTimerQueue.DeleteTimer(this);
  424. }
  425. }
  426. }
  427. public bool Close(WaitHandle toSignal)
  428. {
  429. bool success;
  430. bool shouldSignal = false;
  431. lock (_associatedTimerQueue)
  432. {
  433. if (_canceled)
  434. {
  435. success = false;
  436. }
  437. else
  438. {
  439. _canceled = true;
  440. _notifyWhenNoCallbacksRunning = toSignal;
  441. _associatedTimerQueue.DeleteTimer(this);
  442. shouldSignal = _callbacksRunning == 0;
  443. success = true;
  444. }
  445. }
  446. if (shouldSignal)
  447. SignalNoCallbacksRunning();
  448. return success;
  449. }
  450. public ValueTask CloseAsync()
  451. {
  452. lock (_associatedTimerQueue)
  453. {
  454. object notifyWhenNoCallbacksRunning = _notifyWhenNoCallbacksRunning;
  455. // Mark the timer as canceled if it's not already.
  456. if (_canceled)
  457. {
  458. if (notifyWhenNoCallbacksRunning is WaitHandle)
  459. {
  460. // A previous call to Close(WaitHandle) stored a WaitHandle. We could try to deal with
  461. // this case by using ThreadPool.RegisterWaitForSingleObject to create a Task that'll
  462. // complete when the WaitHandle is set, but since arbitrary WaitHandle's can be supplied
  463. // by the caller, it could be for an auto-reset event or similar where that caller's
  464. // WaitOne on the WaitHandle could prevent this wrapper Task from completing. We could also
  465. // change the implementation to support storing multiple objects, but that's not pay-for-play,
  466. // and the existing Close(WaitHandle) already discounts this as being invalid, instead just
  467. // returning false if you use it multiple times. Since first calling Timer.Dispose(WaitHandle)
  468. // and then calling Timer.DisposeAsync is not something anyone is likely to or should do, we
  469. // simplify by just failing in that case.
  470. return new ValueTask(Task.FromException(new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed)));
  471. }
  472. }
  473. else
  474. {
  475. _canceled = true;
  476. _associatedTimerQueue.DeleteTimer(this);
  477. }
  478. // We've deleted the timer, so if there are no callbacks queued or running,
  479. // we're done and return an already-completed value task.
  480. if (_callbacksRunning == 0)
  481. {
  482. return default;
  483. }
  484. Debug.Assert(
  485. notifyWhenNoCallbacksRunning == null ||
  486. notifyWhenNoCallbacksRunning is Task<bool>);
  487. // There are callbacks queued or running, so we need to store a Task<bool>
  488. // that'll be used to signal the caller when all callbacks complete. Do so as long as
  489. // there wasn't a previous CloseAsync call that did.
  490. if (notifyWhenNoCallbacksRunning == null)
  491. {
  492. var t = new Task<bool>((object)null, TaskCreationOptions.RunContinuationsAsynchronously);
  493. _notifyWhenNoCallbacksRunning = t;
  494. return new ValueTask(t);
  495. }
  496. // A previous CloseAsync call already hooked up a task. Just return it.
  497. return new ValueTask((Task<bool>)notifyWhenNoCallbacksRunning);
  498. }
  499. }
  500. void IThreadPoolWorkItem.Execute() => Fire(isThreadPool: true);
  501. internal void Fire(bool isThreadPool = false)
  502. {
  503. bool canceled = false;
  504. lock (_associatedTimerQueue)
  505. {
  506. canceled = _canceled;
  507. if (!canceled)
  508. _callbacksRunning++;
  509. }
  510. if (canceled)
  511. return;
  512. CallCallback(isThreadPool);
  513. bool shouldSignal = false;
  514. lock (_associatedTimerQueue)
  515. {
  516. _callbacksRunning--;
  517. if (_canceled && _callbacksRunning == 0 && _notifyWhenNoCallbacksRunning != null)
  518. shouldSignal = true;
  519. }
  520. if (shouldSignal)
  521. SignalNoCallbacksRunning();
  522. }
  523. internal void SignalNoCallbacksRunning()
  524. {
  525. object toSignal = _notifyWhenNoCallbacksRunning;
  526. Debug.Assert(toSignal is WaitHandle || toSignal is Task<bool>);
  527. if (toSignal is WaitHandle wh)
  528. {
  529. EventWaitHandle.Set(wh.SafeWaitHandle);
  530. }
  531. else
  532. {
  533. ((Task<bool>)toSignal).TrySetResult(true);
  534. }
  535. }
  536. internal void CallCallback(bool isThreadPool)
  537. {
  538. if (FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
  539. FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty);
  540. // Call directly if EC flow is suppressed
  541. ExecutionContext context = _executionContext;
  542. if (context == null)
  543. {
  544. _timerCallback(_state);
  545. }
  546. else
  547. {
  548. if (isThreadPool)
  549. {
  550. ExecutionContext.RunFromThreadPoolDispatchLoop(Thread.CurrentThread, context, s_callCallbackInContext, this);
  551. }
  552. else
  553. {
  554. ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
  555. }
  556. }
  557. }
  558. private static readonly ContextCallback s_callCallbackInContext = state =>
  559. {
  560. TimerQueueTimer t = (TimerQueueTimer)state;
  561. t._timerCallback(t._state);
  562. };
  563. }
  564. // TimerHolder serves as an intermediary between Timer and TimerQueueTimer, releasing the TimerQueueTimer
  565. // if the Timer is collected.
  566. // This is necessary because Timer itself cannot use its finalizer for this purpose. If it did,
  567. // then users could control timer lifetimes using GC.SuppressFinalize/ReRegisterForFinalize.
  568. // You might ask, wouldn't that be a good thing? Maybe (though it would be even better to offer this
  569. // via first-class APIs), but Timer has never offered this, and adding it now would be a breaking
  570. // change, because any code that happened to be suppressing finalization of Timer objects would now
  571. // unwittingly be changing the lifetime of those timers.
  572. internal sealed class TimerHolder
  573. {
  574. internal TimerQueueTimer _timer;
  575. public TimerHolder(TimerQueueTimer timer)
  576. {
  577. _timer = timer;
  578. }
  579. ~TimerHolder()
  580. {
  581. // If shutdown has started, another thread may be suspended while holding the timer lock.
  582. // So we can't safely close the timer.
  583. //
  584. // Similarly, we should not close the timer during AD-unload's live-object finalization phase.
  585. // A rude abort may have prevented us from releasing the lock.
  586. //
  587. // Note that in either case, the Timer still won't fire, because ThreadPool threads won't be
  588. // allowed to run anymore.
  589. if (Environment.HasShutdownStarted)
  590. return;
  591. _timer.Close();
  592. }
  593. public void Close()
  594. {
  595. _timer.Close();
  596. GC.SuppressFinalize(this);
  597. }
  598. public bool Close(WaitHandle notifyObject)
  599. {
  600. bool result = _timer.Close(notifyObject);
  601. GC.SuppressFinalize(this);
  602. return result;
  603. }
  604. public ValueTask CloseAsync()
  605. {
  606. ValueTask result = _timer.CloseAsync();
  607. GC.SuppressFinalize(this);
  608. return result;
  609. }
  610. }
  611. public sealed class Timer : MarshalByRefObject, IDisposable, IAsyncDisposable
  612. {
  613. private const uint MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
  614. private TimerHolder _timer;
  615. public Timer(TimerCallback callback,
  616. object state,
  617. int dueTime,
  618. int period) :
  619. this(callback, state, dueTime, period, flowExecutionContext: true)
  620. {
  621. }
  622. internal Timer(TimerCallback callback,
  623. object state,
  624. int dueTime,
  625. int period,
  626. bool flowExecutionContext)
  627. {
  628. if (dueTime < -1)
  629. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  630. if (period < -1)
  631. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  632. TimerSetup(callback, state, (uint)dueTime, (uint)period, flowExecutionContext);
  633. }
  634. public Timer(TimerCallback callback,
  635. object state,
  636. TimeSpan dueTime,
  637. TimeSpan period)
  638. {
  639. long dueTm = (long)dueTime.TotalMilliseconds;
  640. if (dueTm < -1)
  641. throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  642. if (dueTm > MAX_SUPPORTED_TIMEOUT)
  643. throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge);
  644. long periodTm = (long)period.TotalMilliseconds;
  645. if (periodTm < -1)
  646. throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  647. if (periodTm > MAX_SUPPORTED_TIMEOUT)
  648. throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge);
  649. TimerSetup(callback, state, (uint)dueTm, (uint)periodTm);
  650. }
  651. [CLSCompliant(false)]
  652. public Timer(TimerCallback callback,
  653. object state,
  654. uint dueTime,
  655. uint period)
  656. {
  657. TimerSetup(callback, state, dueTime, period);
  658. }
  659. public Timer(TimerCallback callback,
  660. object state,
  661. long dueTime,
  662. long period)
  663. {
  664. if (dueTime < -1)
  665. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  666. if (period < -1)
  667. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  668. if (dueTime > MAX_SUPPORTED_TIMEOUT)
  669. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
  670. if (period > MAX_SUPPORTED_TIMEOUT)
  671. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
  672. TimerSetup(callback, state, (uint)dueTime, (uint)period);
  673. }
  674. public Timer(TimerCallback callback)
  675. {
  676. int dueTime = -1; // we want timer to be registered, but not activated. Requires caller to call
  677. int period = -1; // Change after a timer instance is created. This is to avoid the potential
  678. // for a timer to be fired before the returned value is assigned to the variable,
  679. // potentially causing the callback to reference a bogus value (if passing the timer to the callback).
  680. TimerSetup(callback, this, (uint)dueTime, (uint)period);
  681. }
  682. private void TimerSetup(TimerCallback callback,
  683. object state,
  684. uint dueTime,
  685. uint period,
  686. bool flowExecutionContext = true)
  687. {
  688. if (callback == null)
  689. throw new ArgumentNullException(nameof(TimerCallback));
  690. _timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period, flowExecutionContext));
  691. }
  692. public bool Change(int dueTime, int period)
  693. {
  694. if (dueTime < -1)
  695. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  696. if (period < -1)
  697. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  698. return _timer._timer.Change((uint)dueTime, (uint)period);
  699. }
  700. public bool Change(TimeSpan dueTime, TimeSpan period)
  701. {
  702. return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
  703. }
  704. [CLSCompliant(false)]
  705. public bool Change(uint dueTime, uint period)
  706. {
  707. return _timer._timer.Change(dueTime, period);
  708. }
  709. public bool Change(long dueTime, long period)
  710. {
  711. if (dueTime < -1)
  712. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  713. if (period < -1)
  714. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
  715. if (dueTime > MAX_SUPPORTED_TIMEOUT)
  716. throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
  717. if (period > MAX_SUPPORTED_TIMEOUT)
  718. throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
  719. return _timer._timer.Change((uint)dueTime, (uint)period);
  720. }
  721. public bool Dispose(WaitHandle notifyObject)
  722. {
  723. if (notifyObject == null)
  724. throw new ArgumentNullException(nameof(notifyObject));
  725. return _timer.Close(notifyObject);
  726. }
  727. public void Dispose()
  728. {
  729. _timer.Close();
  730. }
  731. public ValueTask DisposeAsync()
  732. {
  733. return _timer.CloseAsync();
  734. }
  735. }
  736. }