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