< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Security.TimeBoundedCache
Assembly: CoreWCF.Primitives
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/Security/TimeBoundedCache.cs
Line coverage
45%
Covered lines: 87
Uncovered lines: 105
Coverable lines: 192
Total lines: 482
Line coverage: 45.3%
Branch coverage
36%
Covered branches: 36
Total branches: 100
Branch coverage: 36%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%11100%
TryAddItem(...)100%110%
CancelTimerIfNeeded()100%44100%
StartTimerIfNeeded()75%4483.33%
TryAddItem(...)50%121280.95%
TryReplaceItem(...)0%10100%
ClearItems()0%660%
GetItem(...)66.66%6685.71%
OnQuotaReached(...)100%110%
OnRemove(...)100%11100%
TryRemoveItem(...)87.5%88100%
EnforceQuota()30%101027.27%
ExtractItem(...)50%2275%
IsExpired(...)100%11100%
ShouldPurge()50%8860%
PurgeIfNeeded()50%4460%
PurgeStaleItems()0%880%
ThrowQuotaReachedException()100%110%
PurgeCallbackStatic(...)0%880%
Compare(...)0%660%
.ctor(...)100%110%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.Primitives/src/CoreWCF/Security/TimeBoundedCache.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.Collections;
 6using System.Collections.Generic;
 7using System.Threading;
 8using CoreWCF.Runtime;
 9
 10namespace CoreWCF.Security
 11{
 12    // NOTE: this class does minimum argument checking as it is all internal
 13    internal class TimeBoundedCache
 14    {
 15        private static Action<object> s_purgeCallback;
 16
 17        // if there are less than lowWaterMark entries, no purging is done
 18        private readonly int _lowWaterMark;
 19        private DateTime _nextPurgeTimeUtc;
 20        private TimeSpan _purgeInterval;
 21        private readonly PurgingMode _purgingMode;
 22        private IOThreadTimer _purgingTimer;
 23        private readonly bool _doRemoveNotification;
 24
 2425        protected TimeBoundedCache(int lowWaterMark, int maxCacheItems, IEqualityComparer keyComparer, PurgingMode purgi
 26        {
 2427            Entries = new Hashtable(keyComparer);
 2428            CacheLock = new ReaderWriterLock();
 2429            _lowWaterMark = lowWaterMark;
 2430            Capacity = maxCacheItems;
 2431            _purgingMode = purgingMode;
 2432            _purgeInterval = purgeInterval;
 2433            _doRemoveNotification = doRemoveNotification;
 2434            _nextPurgeTimeUtc = DateTime.UtcNow.Add(_purgeInterval);
 2435        }
 36
 4037        public int Count => Entries.Count;
 38
 39        private static Action<object> PurgeCallback
 40        {
 41            get
 42            {
 1043                if (s_purgeCallback == null)
 44                {
 245                    s_purgeCallback = new Action<object>(PurgeCallbackStatic);
 46                }
 1047                return s_purgeCallback;
 48            }
 49        }
 50
 3051        protected int Capacity { get; }
 52
 11053        protected Hashtable Entries { get; }
 54
 13055        protected ReaderWriterLock CacheLock { get; }
 56
 57        protected bool TryAddItem(object key, object item, DateTime expirationTime, bool replaceExistingEntry)
 58        {
 059            return TryAddItem(key, new ExpirableItem(item, expirationTime), replaceExistingEntry);
 60        }
 61
 62        private void CancelTimerIfNeeded()
 63        {
 1064            if (Count == 0 && _purgingTimer != null)
 65            {
 1066                _purgingTimer.Cancel();
 1067                _purgingTimer = null;
 68            }
 1069        }
 70
 71        private void StartTimerIfNeeded()
 72        {
 1073            if (_purgingMode != PurgingMode.TimerBasedPurge)
 74            {
 075                return;
 76            }
 1077            if (_purgingTimer == null)
 78            {
 1079                _purgingTimer = new IOThreadTimer(PurgeCallback, this, false);
 1080                _purgingTimer.Set(_purgeInterval);
 81            }
 1082        }
 83
 84        protected bool TryAddItem(object key, IExpirableItem item, bool replaceExistingEntry)
 85        {
 1086            bool lockHeld = false;
 87            try
 88            {
 1089                try { }
 90                finally
 91                {
 1092                    CacheLock.AcquireWriterLock(-1);
 1093                    lockHeld = true;
 1094                }
 1095                PurgeIfNeeded();
 1096                EnforceQuota();
 1097                IExpirableItem currentItem = Entries[key] as IExpirableItem;
 1098                if (currentItem == null || IsExpired(currentItem))
 99                {
 10100                    Entries[key] = item;
 101                }
 0102                else if (!replaceExistingEntry)
 103                {
 0104                    return false;
 105                }
 106                else
 107                {
 0108                    Entries[key] = item;
 109                }
 10110                if (currentItem != null && _doRemoveNotification)
 111                {
 0112                    OnRemove(ExtractItem(currentItem));
 113                }
 10114                StartTimerIfNeeded();
 10115                return true;
 116            }
 117            finally
 118            {
 10119                if (lockHeld)
 120                {
 10121                    CacheLock.ReleaseWriterLock();
 122                }
 10123            }
 10124        }
 125
 126        protected bool TryReplaceItem(object key, object item, DateTime expirationTime)
 127        {
 0128            bool lockHeld = false;
 129            try
 130            {
 0131                try { }
 132                finally
 133                {
 0134                    CacheLock.AcquireWriterLock(-1);
 0135                    lockHeld = true;
 0136                }
 0137                PurgeIfNeeded();
 0138                EnforceQuota();
 0139                if (!(Entries[key] is IExpirableItem currentItem) || IsExpired(currentItem))
 140                {
 0141                    return false;
 142                }
 143                else
 144                {
 0145                    Entries[key] = new ExpirableItem(item, expirationTime);
 0146                    if (currentItem != null && _doRemoveNotification)
 147                    {
 0148                        OnRemove(ExtractItem(currentItem));
 149                    }
 0150                    StartTimerIfNeeded();
 0151                    return true;
 152                }
 153            }
 154            finally
 155            {
 0156                if (lockHeld)
 157                {
 0158                    CacheLock.ReleaseWriterLock();
 159                }
 0160            }
 0161        }
 162
 163        protected void ClearItems()
 164        {
 0165            bool lockHeld = false;
 166            try
 167            {
 0168                try { }
 169                finally
 170                {
 0171                    CacheLock.AcquireWriterLock(-1);
 0172                    lockHeld = true;
 0173                }
 174
 0175                int count = Entries.Count;
 0176                if (_doRemoveNotification)
 177                {
 0178                    foreach (IExpirableItem item in Entries.Values)
 179                    {
 0180                        OnRemove(ExtractItem(item));
 181                    }
 182                }
 0183                Entries.Clear();
 0184                CancelTimerIfNeeded();
 0185            }
 186            finally
 187            {
 0188                if (lockHeld)
 189                {
 0190                    CacheLock.ReleaseWriterLock();
 191                }
 0192            }
 0193        }
 194
 195        protected object GetItem(object key)
 196        {
 20197            bool lockHeld = false;
 198            try
 199            {
 20200                try { }
 201                finally
 202                {
 20203                    CacheLock.AcquireReaderLock(-1);
 20204                    lockHeld = true;
 20205                }
 20206                if (!(Entries[key] is IExpirableItem item))
 207                {
 0208                    return null;
 209                }
 20210                else if (IsExpired(item))
 211                {
 212                    // this is a stale item
 0213                    return null;
 214                }
 215                else
 216                {
 20217                    return ExtractItem(item);
 218                }
 219            }
 220            finally
 221            {
 20222                if (lockHeld)
 223                {
 20224                    CacheLock.ReleaseReaderLock();
 225                }
 20226            }
 20227        }
 228
 229        protected virtual ArrayList OnQuotaReached(Hashtable cacheTable)
 230        {
 0231            ThrowQuotaReachedException();
 0232            return null;
 233        }
 234
 235        protected virtual void OnRemove(object item)
 236        {
 10237        }
 238
 239        protected bool TryRemoveItem(object key)
 240        {
 10241            bool lockHeld = false;
 242            try
 243            {
 10244                try { }
 245                finally
 246                {
 10247                    CacheLock.AcquireWriterLock(-1);
 10248                    lockHeld = true;
 10249                }
 10250                PurgeIfNeeded();
 10251                IExpirableItem currentItem = Entries[key] as IExpirableItem;
 10252                bool result = (currentItem != null) && !IsExpired(currentItem);
 10253                if (currentItem != null)
 254                {
 10255                    Entries.Remove(key);
 10256                    if (_doRemoveNotification)
 257                    {
 10258                        OnRemove(ExtractItem(currentItem));
 259                    }
 10260                    CancelTimerIfNeeded();
 261                }
 10262                return result;
 263            }
 264            finally
 265            {
 10266                if (lockHeld)
 267                {
 10268                    CacheLock.ReleaseWriterLock();
 269                }
 10270            }
 10271        }
 272
 273        private void EnforceQuota()
 274        {
 10275            if (!(CacheLock.IsWriterLockHeld == true))
 276            {
 277                // we failfast here because if we don't have the lock we could corrupt the cache
 278                Fx.Assert("Cache write lock is not held.");
 0279                DiagnosticUtility.FailFast("Cache write lock is not held.");
 280            }
 10281            if (Count >= Capacity)
 282            {
 283                ArrayList keysToBeRemoved;
 0284                keysToBeRemoved = OnQuotaReached(Entries);
 0285                if (keysToBeRemoved != null)
 286                {
 0287                    for (int i = 0; i < keysToBeRemoved.Count; ++i)
 288                    {
 0289                        Entries.Remove(keysToBeRemoved[i]);
 290                    }
 291                }
 0292                CancelTimerIfNeeded();
 0293                if (Count >= Capacity)
 294                {
 0295                    ThrowQuotaReachedException();
 296                }
 297            }
 10298        }
 299
 300        protected object ExtractItem(IExpirableItem val)
 301        {
 30302            ExpirableItem wrapper = (val as ExpirableItem);
 30303            if (wrapper != null)
 304            {
 0305                return wrapper.Item;
 306            }
 307            else
 308            {
 30309                return val;
 310            }
 311        }
 312
 313        private bool IsExpired(IExpirableItem item)
 314        {
 315            Fx.Assert(item.ExpirationTime == DateTime.MaxValue || item.ExpirationTime.Kind == DateTimeKind.Utc, "");
 30316            return (item.ExpirationTime <= DateTime.UtcNow);
 317        }
 318
 319        private bool ShouldPurge()
 320        {
 20321            if (Count >= Capacity)
 322            {
 0323                return true;
 324            }
 20325            else if (_purgingMode == PurgingMode.AccessBasedPurge && DateTime.UtcNow > _nextPurgeTimeUtc && Count > _low
 326            {
 0327                return true;
 328            }
 329            else
 330            {
 20331                return false;
 332            }
 333        }
 334
 335        private void PurgeIfNeeded()
 336        {
 20337            if (!(CacheLock.IsWriterLockHeld == true))
 338            {
 339                // we failfast here because if we don't have the lock we could corrupt the cache
 340                Fx.Assert("Cache write lock is not held.");
 0341                DiagnosticUtility.FailFast("Cache write lock is not held.");
 342            }
 20343            if (ShouldPurge())
 344            {
 0345                PurgeStaleItems();
 346            }
 20347        }
 348
 349        /// <summary>
 350        /// This method must be called from within a writer lock
 351        /// </summary>
 352        private void PurgeStaleItems()
 353        {
 0354            if (!(CacheLock.IsWriterLockHeld == true))
 355            {
 356                // we failfast here because if we don't have the lock we could corrupt the cache
 357                Fx.Assert("Cache write lock is not held.");
 0358                DiagnosticUtility.FailFast("Cache write lock is not held.");
 359            }
 0360            ArrayList expiredItems = new ArrayList();
 0361            foreach (object key in Entries.Keys)
 362            {
 0363                IExpirableItem item = Entries[key] as IExpirableItem;
 0364                if (IsExpired(item))
 365                {
 366                    // this is a stale item. Remove!
 0367                    OnRemove(ExtractItem(item));
 0368                    expiredItems.Add(key);
 369                }
 370            }
 0371            for (int i = 0; i < expiredItems.Count; ++i)
 372            {
 0373                Entries.Remove(expiredItems[i]);
 374            }
 0375            CancelTimerIfNeeded();
 0376            _nextPurgeTimeUtc = DateTime.UtcNow.Add(_purgeInterval);
 0377        }
 378
 379        private void ThrowQuotaReachedException()
 380        {
 0381            string message = SR.Format(SR.CacheQuotaReached, Capacity);
 0382            Exception inner = new QuotaExceededException(message);
 0383            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CommunicationException(message, inner));
 384        }
 385
 386        private static void PurgeCallbackStatic(object state)
 387        {
 0388            TimeBoundedCache self = (TimeBoundedCache)state;
 389
 0390            bool lockHeld = false;
 391            try
 392            {
 0393                try { }
 394                finally
 395                {
 0396                    self.CacheLock.AcquireWriterLock(-1);
 0397                    lockHeld = true;
 0398                }
 399
 0400                if (self._purgingTimer == null)
 401                {
 0402                    return;
 403                }
 0404                self.PurgeStaleItems();
 0405                if (self.Count > 0 && self._purgingTimer != null)
 406                {
 0407                    self._purgingTimer.Set(self._purgeInterval);
 408                }
 0409            }
 410            finally
 411            {
 0412                if (lockHeld)
 413                {
 0414                    self.CacheLock.ReleaseWriterLock();
 415                }
 0416            }
 0417        }
 418
 419        internal interface IExpirableItem
 420        {
 421            DateTime ExpirationTime { get; }
 422        }
 423
 424        internal class ExpirableItemComparer : IComparer<IExpirableItem>
 425        {
 426            private static ExpirableItemComparer s_instance;
 427
 428            public static ExpirableItemComparer Default
 429            {
 430                get
 431                {
 0432                    if (s_instance == null)
 433                    {
 0434                        s_instance = new ExpirableItemComparer();
 435                    }
 0436                    return s_instance;
 437                }
 438            }
 439
 440            // positive, if item1 will expire before item2.
 441            public int Compare(IExpirableItem item1, IExpirableItem item2)
 442            {
 0443                if (ReferenceEquals(item1, item2))
 444                {
 0445                    return 0;
 446                }
 447                Fx.Assert(item1.ExpirationTime.Kind == item2.ExpirationTime.Kind, "");
 0448                if (item1.ExpirationTime < item2.ExpirationTime)
 449                {
 0450                    return 1;
 451                }
 0452                else if (item1.ExpirationTime > item2.ExpirationTime)
 453                {
 0454                    return -1;
 455                }
 456                else
 457                {
 0458                    return 0;
 459                }
 460            }
 461        }
 462
 463        internal sealed class ExpirableItem : IExpirableItem
 464        {
 0465            public ExpirableItem(object item, DateTime expirationTime)
 466            {
 0467                Item = item;
 468                Fx.Assert(expirationTime == DateTime.MaxValue || expirationTime.Kind == DateTimeKind.Utc, "");
 0469                ExpirationTime = expirationTime;
 0470            }
 471
 0472            public DateTime ExpirationTime { get; }
 0473            public object Item { get; }
 474        }
 475    }
 476
 477    internal enum PurgingMode
 478    {
 479        TimerBasedPurge,
 480        AccessBasedPurge
 481    }
 482}