< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Runtime.IOThreadTimer
Assembly: CoreWCF.Kafka
File(s): /home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Runtime/IOThreadTimer.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 242
Coverable lines: 242
Total lines: 615
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 94
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
.ctor(...)0%220%
Cancel()100%110%
Set(...)0%220%
Set(...)100%110%
SetAt(...)100%110%
Reinitialize(...)100%110%
KillTimers()100%110%
.cctor()100%110%
.ctor()100%110%
Kill()100%110%
Set(...)0%12120%
Cancel(...)0%10100%
EnsureWaitScheduled()0%220%
GetOtherTimerGroup(...)0%220%
OnWaitCallback(...)100%110%
ReactivateWaitableTimers()100%110%
ReactivateWaitableTimer(...)0%440%
ScheduleElapsedTimers(...)100%110%
ScheduleElapsedTimers(...)0%440%
ScheduleWait()100%110%
ScheduleWaitIfAnyTimersLeft()0%880%
UpdateWaitableTimer(...)0%440%
.ctor()100%110%
.ctor()100%110%
DeleteMinTimer()100%110%
DeleteTimer(...)0%220%
InsertTimer(...)0%880%
UpdateTimer(...)0%12120%
DeleteMinTimerCore()0%12120%
.ctor()100%110%
Set(...)0%220%
Kill()100%110%
WaitAny(...)0%880%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Runtime/IOThreadTimer.cs

#LineLine coverage
 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
 4using System;
 5using System.Diagnostics.Contracts;
 6using System.Threading;
 7
 8namespace 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
 042            : this(callback, callbackState, isTypicallyCanceledShortlyAfterBeingSet, maxSkewInMillisecondsDefault)
 43        {
 044        }
 45
 046        public IOThreadTimer(Action<object> callback, object callbackState, bool isTypicallyCanceledShortlyAfterBeingSet
 47        {
 048            _callback = callback;
 049            _callbackState = callbackState;
 050            _maxSkew = Ticks.FromMilliseconds(maxSkewInMilliseconds);
 051            _timerGroup =
 052                (isTypicallyCanceledShortlyAfterBeingSet ? TimerManager.Value.VolatileTimerGroup : TimerManager.Value.St
 053        }
 54
 55        public bool Cancel()
 56        {
 057            return TimerManager.Value.Cancel(this);
 58        }
 59
 60        public void Set(TimeSpan timeFromNow)
 61        {
 062            if (timeFromNow != TimeSpan.MaxValue)
 63            {
 064                SetAt(Ticks.Add(Ticks.Now, Ticks.FromTimeSpan(timeFromNow)));
 65            }
 066        }
 67
 68        public void Set(int millisecondsFromNow)
 69        {
 070            SetAt(Ticks.Add(Ticks.Now, Ticks.FromMilliseconds(millisecondsFromNow)));
 071        }
 72
 73        public void SetAt(long dueTime)
 74        {
 075            TimerManager.Value.Set(this, dueTime);
 076        }
 77
 78        protected void Reinitialize(Action<object> callback, object callbackState)
 79        {
 080            _callback = callback;
 081            _callbackState = callbackState;
 082        }
 83
 84        internal static void KillTimers()
 85        {
 086            TimerManager.Value.Kill();
 087        }
 88
 89        private class TimerManager
 90        {
 91            private const long maxTimeToWaitForMoreTimers = 1000 * TimeSpan.TicksPerMillisecond;
 092            private static readonly TimerManager s_value = new TimerManager();
 93            private readonly Action<object> _onWaitCallback;
 94            private readonly WaitableTimer[] _waitableTimers;
 95            private bool _waitScheduled;
 96
 097            public TimerManager()
 98            {
 099                _onWaitCallback = new Action<object>(OnWaitCallback);
 0100                StableTimerGroup = new TimerGroup();
 0101                VolatileTimerGroup = new TimerGroup();
 0102                _waitableTimers = new WaitableTimer[] { StableTimerGroup.WaitableTimer, VolatileTimerGroup.WaitableTimer
 0103            }
 104
 105            private object ThisLock
 106            {
 0107                get { return this; }
 108            }
 109
 110            public static TimerManager Value
 111            {
 112                get
 113                {
 0114                    return s_value;
 115                }
 116            }
 117
 0118            public TimerGroup StableTimerGroup { get; private set; }
 0119            public TimerGroup VolatileTimerGroup { get; private set; }
 120
 121            internal void Kill()
 122            {
 0123                StableTimerGroup.WaitableTimer.Kill();
 0124                VolatileTimerGroup.WaitableTimer.Kill();
 0125            }
 126
 127            public void Set(IOThreadTimer timer, long dueTime)
 128            {
 0129                long timeDiff = dueTime - timer._dueTime;
 0130                if (timeDiff < 0)
 131                {
 0132                    timeDiff = -timeDiff;
 133                }
 134
 0135                if (timeDiff > timer._maxSkew)
 136                {
 0137                    lock (ThisLock)
 138                    {
 0139                        TimerGroup timerGroup = timer._timerGroup;
 0140                        TimerQueue timerQueue = timerGroup.TimerQueue;
 141
 0142                        if (timer._index > 0)
 143                        {
 0144                            if (timerQueue.UpdateTimer(timer, dueTime))
 145                            {
 0146                                UpdateWaitableTimer(timerGroup);
 147                            }
 148                        }
 149                        else
 150                        {
 0151                            if (timerQueue.InsertTimer(timer, dueTime))
 152                            {
 0153                                UpdateWaitableTimer(timerGroup);
 154
 0155                                if (timerQueue.Count == 1)
 156                                {
 0157                                    EnsureWaitScheduled();
 158                                }
 159                            }
 160                        }
 0161                    }
 162                }
 0163            }
 164
 165            public bool Cancel(IOThreadTimer timer)
 166            {
 0167                lock (ThisLock)
 168                {
 0169                    if (timer._index > 0)
 170                    {
 0171                        TimerGroup timerGroup = timer._timerGroup;
 0172                        TimerQueue timerQueue = timerGroup.TimerQueue;
 173
 0174                        timerQueue.DeleteTimer(timer);
 175
 0176                        if (timerQueue.Count > 0)
 177                        {
 0178                            UpdateWaitableTimer(timerGroup);
 179                        }
 180                        else
 181                        {
 0182                            TimerGroup otherTimerGroup = GetOtherTimerGroup(timerGroup);
 0183                            if (otherTimerGroup.TimerQueue.Count == 0)
 184                            {
 0185                                long now = Ticks.Now;
 0186                                long thisGroupRemainingTime = timerGroup.WaitableTimer.DueTime - now;
 0187                                long otherGroupRemainingTime = otherTimerGroup.WaitableTimer.DueTime - now;
 0188                                if (thisGroupRemainingTime > maxTimeToWaitForMoreTimers &&
 0189                                    otherGroupRemainingTime > maxTimeToWaitForMoreTimers)
 190                                {
 0191                                    timerGroup.WaitableTimer.Set(Ticks.Add(now, maxTimeToWaitForMoreTimers));
 192                                }
 193                            }
 194                        }
 195
 0196                        return true;
 197                    }
 198                    else
 199                    {
 0200                        return false;
 201                    }
 202                }
 0203            }
 204
 205            private void EnsureWaitScheduled()
 206            {
 0207                if (!_waitScheduled)
 208                {
 0209                    ScheduleWait();
 210                }
 0211            }
 212
 213            private TimerGroup GetOtherTimerGroup(TimerGroup timerGroup)
 214            {
 0215                if (ReferenceEquals(timerGroup, VolatileTimerGroup))
 216                {
 0217                    return StableTimerGroup;
 218                }
 219                else
 220                {
 0221                    return VolatileTimerGroup;
 222                }
 223            }
 224
 225            private void OnWaitCallback(object state)
 226            {
 0227                WaitableTimer.WaitAny(_waitableTimers);
 0228                long now = Ticks.Now;
 0229                lock (ThisLock)
 230                {
 0231                    _waitScheduled = false;
 0232                    ScheduleElapsedTimers(now);
 0233                    ReactivateWaitableTimers();
 0234                    ScheduleWaitIfAnyTimersLeft();
 0235                }
 0236            }
 237
 238            private void ReactivateWaitableTimers()
 239            {
 0240                ReactivateWaitableTimer(StableTimerGroup);
 0241                ReactivateWaitableTimer(VolatileTimerGroup);
 0242            }
 243
 244            private void ReactivateWaitableTimer(TimerGroup timerGroup)
 245            {
 0246                TimerQueue timerQueue = timerGroup.TimerQueue;
 247
 0248                if (timerGroup.WaitableTimer.dead)
 249                {
 0250                    return;
 251                }
 252
 0253                if (timerQueue.Count > 0)
 254                {
 0255                    timerGroup.WaitableTimer.Set(timerQueue.MinTimer._dueTime);
 256                }
 257                else
 258                {
 0259                    timerGroup.WaitableTimer.Set(long.MaxValue);
 260                }
 0261            }
 262
 263            private void ScheduleElapsedTimers(long now)
 264            {
 0265                ScheduleElapsedTimers(StableTimerGroup, now);
 0266                ScheduleElapsedTimers(VolatileTimerGroup, now);
 0267            }
 268
 269            private void ScheduleElapsedTimers(TimerGroup timerGroup, long now)
 270            {
 0271                TimerQueue timerQueue = timerGroup.TimerQueue;
 0272                while (timerQueue.Count > 0)
 273                {
 0274                    IOThreadTimer timer = timerQueue.MinTimer;
 0275                    long timeDiff = timer._dueTime - now;
 0276                    if (timeDiff <= timer._maxSkew)
 277                    {
 0278                        timerQueue.DeleteMinTimer();
 0279                        ActionItem.Schedule(timer._callback, timer._callbackState);
 280                    }
 281                    else
 282                    {
 283                        break;
 284                    }
 285                }
 0286            }
 287
 288            private void ScheduleWait()
 289            {
 0290                ActionItem.Schedule(_onWaitCallback, null);
 0291                _waitScheduled = true;
 0292            }
 293
 294            private void ScheduleWaitIfAnyTimersLeft()
 295            {
 0296                if (StableTimerGroup.WaitableTimer.dead &&
 0297                    VolatileTimerGroup.WaitableTimer.dead)
 298                {
 0299                    return;
 300                }
 301
 0302                if (StableTimerGroup.TimerQueue.Count > 0 ||
 0303                    VolatileTimerGroup.TimerQueue.Count > 0)
 304                {
 0305                    ScheduleWait();
 306                }
 0307            }
 308
 309            private void UpdateWaitableTimer(TimerGroup timerGroup)
 310            {
 0311                WaitableTimer waitableTimer = timerGroup.WaitableTimer;
 0312                IOThreadTimer minTimer = timerGroup.TimerQueue.MinTimer;
 0313                long timeDiff = waitableTimer.DueTime - minTimer._dueTime;
 0314                if (timeDiff < 0)
 315                {
 0316                    timeDiff = -timeDiff;
 317                }
 0318                if (timeDiff > minTimer._maxSkew)
 319                {
 0320                    waitableTimer.Set(minTimer._dueTime);
 321                }
 0322            }
 323        }
 324
 325        private class TimerGroup
 326        {
 0327            public TimerGroup()
 328            {
 0329                WaitableTimer = new WaitableTimer();
 0330                TimerQueue = new TimerQueue();
 0331            }
 332
 0333            public TimerQueue TimerQueue { get; private set; }
 0334            public WaitableTimer WaitableTimer { get; private set; }
 335        }
 336
 337        private class TimerQueue
 338        {
 339            private IOThreadTimer[] _timers;
 340
 0341            public TimerQueue()
 342            {
 0343                _timers = new IOThreadTimer[4];
 0344            }
 345
 0346            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.");
 0353                    return _timers[1];
 354                }
 355            }
 356            public void DeleteMinTimer()
 357            {
 0358                IOThreadTimer minTimer = MinTimer;
 0359                DeleteMinTimerCore();
 0360                minTimer._index = 0;
 0361                minTimer._dueTime = 0;
 0362            }
 363            public void DeleteTimer(IOThreadTimer timer)
 364            {
 0365                int index = timer._index;
 366
 367                Fx.Assert(index > 0, "");
 368                Fx.Assert(index <= Count, "");
 369
 0370                IOThreadTimer[] timers = _timers;
 371
 372                for (; ; )
 373                {
 0374                    int parentIndex = index / 2;
 375
 0376                    if (parentIndex >= 1)
 377                    {
 0378                        IOThreadTimer parentTimer = timers[parentIndex];
 0379                        timers[index] = parentTimer;
 0380                        parentTimer._index = index;
 381                    }
 382                    else
 383                    {
 384                        break;
 385                    }
 386
 0387                    index = parentIndex;
 388                }
 389
 0390                timer._index = 0;
 0391                timer._dueTime = 0;
 0392                timers[1] = null;
 0393                DeleteMinTimerCore();
 0394            }
 395
 396            public bool InsertTimer(IOThreadTimer timer, long dueTime)
 397            {
 398                Fx.Assert(timer._index == 0, "Timer should not have an index.");
 399
 0400                IOThreadTimer[] timers = _timers;
 401
 0402                int index = Count + 1;
 403
 0404                if (index == timers.Length)
 405                {
 0406                    timers = new IOThreadTimer[timers.Length * 2];
 0407                    Array.Copy(_timers, timers, _timers.Length);
 0408                    _timers = timers;
 409                }
 410
 0411                Count = index;
 412
 0413                if (index > 1)
 414                {
 415                    for (; ; )
 416                    {
 0417                        int parentIndex = index / 2;
 418
 0419                        if (parentIndex == 0)
 420                        {
 421                            break;
 422                        }
 423
 0424                        IOThreadTimer parent = timers[parentIndex];
 425
 0426                        if (parent._dueTime > dueTime)
 427                        {
 0428                            timers[index] = parent;
 0429                            parent._index = index;
 0430                            index = parentIndex;
 431                        }
 432                        else
 433                        {
 434                            break;
 435                        }
 436                    }
 437                }
 438
 0439                timers[index] = timer;
 0440                timer._index = index;
 0441                timer._dueTime = dueTime;
 0442                return index == 1;
 443            }
 444            public bool UpdateTimer(IOThreadTimer timer, long dueTime)
 445            {
 0446                int index = timer._index;
 447
 0448                IOThreadTimer[] timers = _timers;
 0449                int count = Count;
 450
 451                Fx.Assert(index > 0, "");
 452                Fx.Assert(index <= count, "");
 453
 0454                int parentIndex = index / 2;
 0455                if (parentIndex == 0 ||
 0456                    timers[parentIndex]._dueTime <= dueTime)
 457                {
 0458                    int leftChildIndex = index * 2;
 0459                    if (leftChildIndex > count ||
 0460                        timers[leftChildIndex]._dueTime >= dueTime)
 461                    {
 0462                        int rightChildIndex = leftChildIndex + 1;
 0463                        if (rightChildIndex > count ||
 0464                            timers[rightChildIndex]._dueTime >= dueTime)
 465                        {
 0466                            timer._dueTime = dueTime;
 0467                            return index == 1;
 468                        }
 469                    }
 470                }
 471
 0472                DeleteTimer(timer);
 0473                InsertTimer(timer, dueTime);
 0474                return true;
 475            }
 476
 477            private void DeleteMinTimerCore()
 478            {
 0479                int count = Count;
 480
 0481                if (count == 1)
 482                {
 0483                    Count = 0;
 0484                    _timers[1] = null;
 485                }
 486                else
 487                {
 0488                    IOThreadTimer[] timers = _timers;
 0489                    IOThreadTimer lastTimer = timers[count];
 0490                    Count = --count;
 491
 0492                    int index = 1;
 493                    for (; ; )
 494                    {
 0495                        int leftChildIndex = index * 2;
 496
 0497                        if (leftChildIndex > count)
 498                        {
 499                            break;
 500                        }
 501
 502                        int childIndex;
 503                        IOThreadTimer child;
 504
 0505                        if (leftChildIndex < count)
 506                        {
 0507                            IOThreadTimer leftChild = timers[leftChildIndex];
 0508                            int rightChildIndex = leftChildIndex + 1;
 0509                            IOThreadTimer rightChild = timers[rightChildIndex];
 510
 0511                            if (rightChild._dueTime < leftChild._dueTime)
 512                            {
 0513                                child = rightChild;
 0514                                childIndex = rightChildIndex;
 515                            }
 516                            else
 517                            {
 0518                                child = leftChild;
 0519                                childIndex = leftChildIndex;
 520                            }
 521                        }
 522                        else
 523                        {
 0524                            childIndex = leftChildIndex;
 0525                            child = timers[childIndex];
 526                        }
 527
 0528                        if (lastTimer._dueTime > child._dueTime)
 529                        {
 0530                            timers[index] = child;
 0531                            child._index = index;
 532                        }
 533                        else
 534                        {
 535                            break;
 536                        }
 537
 0538                        index = childIndex;
 539
 0540                        if (leftChildIndex >= count)
 541                        {
 542                            break;
 543                        }
 544                    }
 545
 0546                    timers[index] = lastTimer;
 0547                    lastTimer._index = index;
 0548                    timers[count + 1] = null;
 549                }
 0550            }
 551        }
 552
 553        public class WaitableTimer : EventWaitHandle
 554        {
 555            public bool dead;
 556
 0557            public WaitableTimer() : base(false, EventResetMode.AutoReset)
 558            {
 0559            }
 560
 0561            public long DueTime { get; private set; }
 562
 563            public void Set(long dueTime)
 564            {
 0565                if (dueTime < DueTime)
 566                {
 0567                    DueTime = dueTime;
 0568                    Set(); // We might be waiting on a later time so nudge it to reworkout the time
 569                }
 570                else
 571                {
 0572                    DueTime = dueTime;
 573                }
 0574            }
 575
 576            public void Kill()
 577            {
 0578                dead = true;
 0579                Set();
 0580            }
 581
 582            public static int WaitAny(WaitableTimer[] waitableTimers)
 583            {
 584                do
 585                {
 0586                    long earliestDueTime = waitableTimers[0].DueTime;
 0587                    for (int i = 1; i < waitableTimers.Length; i++)
 588                    {
 0589                        if (waitableTimers[i].dead)
 590                        {
 0591                            return 0;
 592                        }
 593
 0594                        if (waitableTimers[i].DueTime < earliestDueTime)
 595                        {
 0596                            earliestDueTime = waitableTimers[i].DueTime;
 597                        }
 598
 0599                        waitableTimers[i].Reset();
 600                    }
 601
 0602                    long waitDurationInMillis = (earliestDueTime - DateTime.UtcNow.Ticks) / TimeSpan.TicksPerMillisecond
 0603                    if (waitDurationInMillis < 0) // Already passed the due time
 604                    {
 0605                        return 0;
 606                    }
 607
 608                    Contract.Assert(waitDurationInMillis < int.MaxValue, "Waiting for longer than is possible");
 0609                    WaitAny(waitableTimers, (int)waitDurationInMillis);
 610                    // Always loop around and check wait time again as values might have changed.
 0611                } while (true);
 612            }
 613        }
 614    }
 615}