IOThreadTimer.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. //------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. //------------------------------------------------------------
  4. namespace System.Runtime
  5. {
  6. using System;
  7. using System.ComponentModel;
  8. using System.Runtime.Interop;
  9. using System.Security;
  10. using System.Threading;
  11. using Microsoft.Win32.SafeHandles;
  12. // IOThreadTimer has several characterstics that are important for performance:
  13. // - Timers that expire benefit from being scheduled to run on IO threads using IOThreadScheduler.Schedule.
  14. // - The timer "waiter" thread thread is only allocated if there are set timers.
  15. // - The timer waiter thread itself is an IO thread, which allows it to go away if there is no need for it,
  16. // and allows it to be reused for other purposes.
  17. // - After the timer count goes to zero, the timer waiter thread remains active for a bounded amount
  18. // of time to wait for additional timers to be set.
  19. // - Timers are stored in an array-based priority queue to reduce the amount of time spent in updates, and
  20. // to always provide O(1) access to the minimum timer (the first one that will expire).
  21. // - The standard textbook priority queue data structure is extended to allow efficient Delete in addition to
  22. // DeleteMin for efficient handling of canceled timers.
  23. // - Timers that are typically set, then immediately canceled (such as a retry timer,
  24. // or a flush timer), are tracked separately from more stable timers, to avoid having
  25. // to update the waitable timer in the typical case when a timer is canceled. Whether
  26. // a timer instance follows this pattern is specified when the timer is constructed.
  27. // - Extending a timer by a configurable time delta (maxSkew) does not involve updating the
  28. // waitable timer, or taking a lock.
  29. // - Timer instances are relatively cheap. They share "heavy" resources like the waiter thread and
  30. // waitable timer handle.
  31. // - Setting or canceling a timer does not typically involve any allocations.
  32. class IOThreadTimer
  33. {
  34. const int maxSkewInMillisecondsDefault = 100;
  35. static long systemTimeResolutionTicks = -1;
  36. Action<object> callback;
  37. object callbackState;
  38. long dueTime;
  39. int index;
  40. long maxSkew;
  41. TimerGroup timerGroup;
  42. public IOThreadTimer(Action<object> callback, object callbackState, bool isTypicallyCanceledShortlyAfterBeingSet)
  43. : this(callback, callbackState, isTypicallyCanceledShortlyAfterBeingSet, maxSkewInMillisecondsDefault)
  44. {
  45. }
  46. public IOThreadTimer(Action<object> callback, object callbackState, bool isTypicallyCanceledShortlyAfterBeingSet, int maxSkewInMilliseconds)
  47. {
  48. this.callback = callback;
  49. this.callbackState = callbackState;
  50. this.maxSkew = Ticks.FromMilliseconds(maxSkewInMilliseconds);
  51. this.timerGroup =
  52. (isTypicallyCanceledShortlyAfterBeingSet ? TimerManager.Value.VolatileTimerGroup : TimerManager.Value.StableTimerGroup);
  53. }
  54. public static long SystemTimeResolutionTicks
  55. {
  56. get
  57. {
  58. if (IOThreadTimer.systemTimeResolutionTicks == -1)
  59. {
  60. IOThreadTimer.systemTimeResolutionTicks = GetSystemTimeResolution();
  61. }
  62. return IOThreadTimer.systemTimeResolutionTicks;
  63. }
  64. }
  65. [Fx.Tag.SecurityNote(Critical = "Calls critical method GetSystemTimeAdjustment", Safe = "method is a SafeNativeMethod")]
  66. [SecuritySafeCritical]
  67. static long GetSystemTimeResolution()
  68. {
  69. int dummyAdjustment;
  70. uint increment;
  71. uint dummyAdjustmentDisabled;
  72. if (UnsafeNativeMethods.GetSystemTimeAdjustment(out dummyAdjustment, out increment, out dummyAdjustmentDisabled) != 0)
  73. {
  74. return (long)increment;
  75. }
  76. // Assume the default, which is around 15 milliseconds.
  77. return 15 * TimeSpan.TicksPerMillisecond;
  78. }
  79. public bool Cancel()
  80. {
  81. return TimerManager.Value.Cancel(this);
  82. }
  83. public void Set(TimeSpan timeFromNow)
  84. {
  85. if (timeFromNow != TimeSpan.MaxValue)
  86. {
  87. SetAt(Ticks.Add(Ticks.Now, Ticks.FromTimeSpan(timeFromNow)));
  88. }
  89. }
  90. public void Set(int millisecondsFromNow)
  91. {
  92. SetAt(Ticks.Add(Ticks.Now, Ticks.FromMilliseconds(millisecondsFromNow)));
  93. }
  94. public void SetAt(long dueTime)
  95. {
  96. TimerManager.Value.Set(this, dueTime);
  97. }
  98. [Fx.Tag.SynchronizationObject(Blocking = false, Scope = Fx.Tag.Strings.AppDomain)]
  99. class TimerManager
  100. {
  101. const long maxTimeToWaitForMoreTimers = 1000 * TimeSpan.TicksPerMillisecond;
  102. [Fx.Tag.Queue(typeof(IOThreadTimer), Scope = Fx.Tag.Strings.AppDomain, StaleElementsRemovedImmediately = true)]
  103. static TimerManager value = new TimerManager();
  104. Action<object> onWaitCallback;
  105. TimerGroup stableTimerGroup;
  106. TimerGroup volatileTimerGroup;
  107. [Fx.Tag.SynchronizationObject(Blocking = false)]
  108. WaitableTimer[] waitableTimers;
  109. bool waitScheduled;
  110. public TimerManager()
  111. {
  112. this.onWaitCallback = new Action<object>(OnWaitCallback);
  113. this.stableTimerGroup = new TimerGroup();
  114. this.volatileTimerGroup = new TimerGroup();
  115. this.waitableTimers = new WaitableTimer[] { this.stableTimerGroup.WaitableTimer, this.volatileTimerGroup.WaitableTimer };
  116. }
  117. object ThisLock
  118. {
  119. get { return this; }
  120. }
  121. public static TimerManager Value
  122. {
  123. get
  124. {
  125. return TimerManager.value;
  126. }
  127. }
  128. public TimerGroup StableTimerGroup
  129. {
  130. get
  131. {
  132. return this.stableTimerGroup;
  133. }
  134. }
  135. public TimerGroup VolatileTimerGroup
  136. {
  137. get
  138. {
  139. return this.volatileTimerGroup;
  140. }
  141. }
  142. public void Set(IOThreadTimer timer, long dueTime)
  143. {
  144. long timeDiff = dueTime - timer.dueTime;
  145. if (timeDiff < 0)
  146. {
  147. timeDiff = -timeDiff;
  148. }
  149. if (timeDiff > timer.maxSkew)
  150. {
  151. lock (ThisLock)
  152. {
  153. TimerGroup timerGroup = timer.timerGroup;
  154. TimerQueue timerQueue = timerGroup.TimerQueue;
  155. if (timer.index > 0)
  156. {
  157. if (timerQueue.UpdateTimer(timer, dueTime))
  158. {
  159. UpdateWaitableTimer(timerGroup);
  160. }
  161. }
  162. else
  163. {
  164. if (timerQueue.InsertTimer(timer, dueTime))
  165. {
  166. UpdateWaitableTimer(timerGroup);
  167. if (timerQueue.Count == 1)
  168. {
  169. EnsureWaitScheduled();
  170. }
  171. }
  172. }
  173. }
  174. }
  175. }
  176. public bool Cancel(IOThreadTimer timer)
  177. {
  178. lock (ThisLock)
  179. {
  180. if (timer.index > 0)
  181. {
  182. TimerGroup timerGroup = timer.timerGroup;
  183. TimerQueue timerQueue = timerGroup.TimerQueue;
  184. timerQueue.DeleteTimer(timer);
  185. if (timerQueue.Count > 0)
  186. {
  187. UpdateWaitableTimer(timerGroup);
  188. }
  189. else
  190. {
  191. TimerGroup otherTimerGroup = GetOtherTimerGroup(timerGroup);
  192. if (otherTimerGroup.TimerQueue.Count == 0)
  193. {
  194. long now = Ticks.Now;
  195. long thisGroupRemainingTime = timerGroup.WaitableTimer.DueTime - now;
  196. long otherGroupRemainingTime = otherTimerGroup.WaitableTimer.DueTime - now;
  197. if (thisGroupRemainingTime > maxTimeToWaitForMoreTimers &&
  198. otherGroupRemainingTime > maxTimeToWaitForMoreTimers)
  199. {
  200. timerGroup.WaitableTimer.Set(Ticks.Add(now, maxTimeToWaitForMoreTimers));
  201. }
  202. }
  203. }
  204. return true;
  205. }
  206. else
  207. {
  208. return false;
  209. }
  210. }
  211. }
  212. void EnsureWaitScheduled()
  213. {
  214. if (!this.waitScheduled)
  215. {
  216. ScheduleWait();
  217. }
  218. }
  219. TimerGroup GetOtherTimerGroup(TimerGroup timerGroup)
  220. {
  221. if (object.ReferenceEquals(timerGroup, this.volatileTimerGroup))
  222. {
  223. return this.stableTimerGroup;
  224. }
  225. else
  226. {
  227. return this.volatileTimerGroup;
  228. }
  229. }
  230. void OnWaitCallback(object state)
  231. {
  232. WaitHandle.WaitAny(this.waitableTimers);
  233. long now = Ticks.Now;
  234. lock (ThisLock)
  235. {
  236. this.waitScheduled = false;
  237. ScheduleElapsedTimers(now);
  238. ReactivateWaitableTimers();
  239. ScheduleWaitIfAnyTimersLeft();
  240. }
  241. }
  242. void ReactivateWaitableTimers()
  243. {
  244. ReactivateWaitableTimer(this.stableTimerGroup);
  245. ReactivateWaitableTimer(this.volatileTimerGroup);
  246. }
  247. void ReactivateWaitableTimer(TimerGroup timerGroup)
  248. {
  249. TimerQueue timerQueue = timerGroup.TimerQueue;
  250. if (timerQueue.Count > 0)
  251. {
  252. timerGroup.WaitableTimer.Set(timerQueue.MinTimer.dueTime);
  253. }
  254. else
  255. {
  256. timerGroup.WaitableTimer.Set(long.MaxValue);
  257. }
  258. }
  259. void ScheduleElapsedTimers(long now)
  260. {
  261. ScheduleElapsedTimers(this.stableTimerGroup, now);
  262. ScheduleElapsedTimers(this.volatileTimerGroup, now);
  263. }
  264. void ScheduleElapsedTimers(TimerGroup timerGroup, long now)
  265. {
  266. TimerQueue timerQueue = timerGroup.TimerQueue;
  267. while (timerQueue.Count > 0)
  268. {
  269. IOThreadTimer timer = timerQueue.MinTimer;
  270. long timeDiff = timer.dueTime - now;
  271. if (timeDiff <= timer.maxSkew)
  272. {
  273. timerQueue.DeleteMinTimer();
  274. ActionItem.Schedule(timer.callback, timer.callbackState);
  275. }
  276. else
  277. {
  278. break;
  279. }
  280. }
  281. }
  282. void ScheduleWait()
  283. {
  284. ActionItem.Schedule(this.onWaitCallback, null);
  285. this.waitScheduled = true;
  286. }
  287. void ScheduleWaitIfAnyTimersLeft()
  288. {
  289. if (this.stableTimerGroup.TimerQueue.Count > 0 ||
  290. this.volatileTimerGroup.TimerQueue.Count > 0)
  291. {
  292. ScheduleWait();
  293. }
  294. }
  295. void UpdateWaitableTimer(TimerGroup timerGroup)
  296. {
  297. WaitableTimer waitableTimer = timerGroup.WaitableTimer;
  298. IOThreadTimer minTimer = timerGroup.TimerQueue.MinTimer;
  299. long timeDiff = waitableTimer.DueTime - minTimer.dueTime;
  300. if (timeDiff < 0)
  301. {
  302. timeDiff = -timeDiff;
  303. }
  304. if (timeDiff > minTimer.maxSkew)
  305. {
  306. waitableTimer.Set(minTimer.dueTime);
  307. }
  308. }
  309. }
  310. class TimerGroup
  311. {
  312. TimerQueue timerQueue;
  313. WaitableTimer waitableTimer;
  314. public TimerGroup()
  315. {
  316. this.waitableTimer = new WaitableTimer();
  317. this.waitableTimer.Set(long.MaxValue);
  318. this.timerQueue = new TimerQueue();
  319. }
  320. public TimerQueue TimerQueue
  321. {
  322. get
  323. {
  324. return this.timerQueue;
  325. }
  326. }
  327. public WaitableTimer WaitableTimer
  328. {
  329. get
  330. {
  331. return this.waitableTimer;
  332. }
  333. }
  334. }
  335. class TimerQueue
  336. {
  337. int count;
  338. IOThreadTimer[] timers;
  339. public TimerQueue()
  340. {
  341. this.timers = new IOThreadTimer[4];
  342. }
  343. public int Count
  344. {
  345. get { return count; }
  346. }
  347. public IOThreadTimer MinTimer
  348. {
  349. get
  350. {
  351. Fx.Assert(this.count > 0, "Should have at least one timer in our queue.");
  352. return timers[1];
  353. }
  354. }
  355. public void DeleteMinTimer()
  356. {
  357. IOThreadTimer minTimer = this.MinTimer;
  358. DeleteMinTimerCore();
  359. minTimer.index = 0;
  360. minTimer.dueTime = 0;
  361. }
  362. public void DeleteTimer(IOThreadTimer timer)
  363. {
  364. int index = timer.index;
  365. Fx.Assert(index > 0, "");
  366. Fx.Assert(index <= this.count, "");
  367. IOThreadTimer[] timers = this.timers;
  368. for (;;)
  369. {
  370. int parentIndex = index / 2;
  371. if (parentIndex >= 1)
  372. {
  373. IOThreadTimer parentTimer = timers[parentIndex];
  374. timers[index] = parentTimer;
  375. parentTimer.index = index;
  376. }
  377. else
  378. {
  379. break;
  380. }
  381. index = parentIndex;
  382. }
  383. timer.index = 0;
  384. timer.dueTime = 0;
  385. timers[1] = null;
  386. DeleteMinTimerCore();
  387. }
  388. public bool InsertTimer(IOThreadTimer timer, long dueTime)
  389. {
  390. Fx.Assert(timer.index == 0, "Timer should not have an index.");
  391. IOThreadTimer[] timers = this.timers;
  392. int index = this.count + 1;
  393. if (index == timers.Length)
  394. {
  395. timers = new IOThreadTimer[timers.Length * 2];
  396. Array.Copy(this.timers, timers, this.timers.Length);
  397. this.timers = timers;
  398. }
  399. this.count = index;
  400. if (index > 1)
  401. {
  402. for (;;)
  403. {
  404. int parentIndex = index / 2;
  405. if (parentIndex == 0)
  406. {
  407. break;
  408. }
  409. IOThreadTimer parent = timers[parentIndex];
  410. if (parent.dueTime > dueTime)
  411. {
  412. timers[index] = parent;
  413. parent.index = index;
  414. index = parentIndex;
  415. }
  416. else
  417. {
  418. break;
  419. }
  420. }
  421. }
  422. timers[index] = timer;
  423. timer.index = index;
  424. timer.dueTime = dueTime;
  425. return index == 1;
  426. }
  427. public bool UpdateTimer(IOThreadTimer timer, long dueTime)
  428. {
  429. int index = timer.index;
  430. IOThreadTimer[] timers = this.timers;
  431. int count = this.count;
  432. Fx.Assert(index > 0, "");
  433. Fx.Assert(index <= count, "");
  434. int parentIndex = index / 2;
  435. if (parentIndex == 0 ||
  436. timers[parentIndex].dueTime <= dueTime)
  437. {
  438. int leftChildIndex = index * 2;
  439. if (leftChildIndex > count ||
  440. timers[leftChildIndex].dueTime >= dueTime)
  441. {
  442. int rightChildIndex = leftChildIndex + 1;
  443. if (rightChildIndex > count ||
  444. timers[rightChildIndex].dueTime >= dueTime)
  445. {
  446. timer.dueTime = dueTime;
  447. return index == 1;
  448. }
  449. }
  450. }
  451. DeleteTimer(timer);
  452. InsertTimer(timer, dueTime);
  453. return true;
  454. }
  455. void DeleteMinTimerCore()
  456. {
  457. int count = this.count;
  458. if (count == 1)
  459. {
  460. this.count = 0;
  461. this.timers[1] = null;
  462. }
  463. else
  464. {
  465. IOThreadTimer[] timers = this.timers;
  466. IOThreadTimer lastTimer = timers[count];
  467. this.count = --count;
  468. int index = 1;
  469. for (;;)
  470. {
  471. int leftChildIndex = index * 2;
  472. if (leftChildIndex > count)
  473. {
  474. break;
  475. }
  476. int childIndex;
  477. IOThreadTimer child;
  478. if (leftChildIndex < count)
  479. {
  480. IOThreadTimer leftChild = timers[leftChildIndex];
  481. int rightChildIndex = leftChildIndex + 1;
  482. IOThreadTimer rightChild = timers[rightChildIndex];
  483. if (rightChild.dueTime < leftChild.dueTime)
  484. {
  485. child = rightChild;
  486. childIndex = rightChildIndex;
  487. }
  488. else
  489. {
  490. child = leftChild;
  491. childIndex = leftChildIndex;
  492. }
  493. }
  494. else
  495. {
  496. childIndex = leftChildIndex;
  497. child = timers[childIndex];
  498. }
  499. if (lastTimer.dueTime > child.dueTime)
  500. {
  501. timers[index] = child;
  502. child.index = index;
  503. }
  504. else
  505. {
  506. break;
  507. }
  508. index = childIndex;
  509. if (leftChildIndex >= count)
  510. {
  511. break;
  512. }
  513. }
  514. timers[index] = lastTimer;
  515. lastTimer.index = index;
  516. timers[count + 1] = null;
  517. }
  518. }
  519. }
  520. [Fx.Tag.SynchronizationPrimitive(Fx.Tag.BlocksUsing.NonBlocking)]
  521. class WaitableTimer : WaitHandle
  522. {
  523. long dueTime;
  524. [Fx.Tag.SecurityNote(Critical = "Call the critical CreateWaitableTimer method in TimerHelper",
  525. Safe = "Doesn't leak information or resources")]
  526. [SecuritySafeCritical]
  527. public WaitableTimer()
  528. {
  529. this.SafeWaitHandle = TimerHelper.CreateWaitableTimer();
  530. }
  531. public long DueTime
  532. {
  533. get { return this.dueTime; }
  534. }
  535. [Fx.Tag.SecurityNote(Critical = "Call the critical Set method in TimerHelper",
  536. Safe = "Doesn't leak information or resources")]
  537. [SecuritySafeCritical]
  538. public void Set(long dueTime)
  539. {
  540. this.dueTime = TimerHelper.Set(this.SafeWaitHandle, dueTime);
  541. }
  542. [Fx.Tag.SecurityNote(Critical = "Provides a set of unsafe methods used to work with the WaitableTimer")]
  543. [SecurityCritical]
  544. static class TimerHelper
  545. {
  546. public static unsafe SafeWaitHandle CreateWaitableTimer()
  547. {
  548. SafeWaitHandle handle = UnsafeNativeMethods.CreateWaitableTimer(IntPtr.Zero, false, null);
  549. if (handle.IsInvalid)
  550. {
  551. Exception exception = new Win32Exception();
  552. handle.SetHandleAsInvalid();
  553. throw Fx.Exception.AsError(exception);
  554. }
  555. return handle;
  556. }
  557. public static unsafe long Set(SafeWaitHandle timer, long dueTime)
  558. {
  559. if (!UnsafeNativeMethods.SetWaitableTimer(timer, ref dueTime, 0, IntPtr.Zero, IntPtr.Zero, false))
  560. {
  561. throw Fx.Exception.AsError(new Win32Exception());
  562. }
  563. return dueTime;
  564. }
  565. }
  566. }
  567. }
  568. }