< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Runtime.Collections.HopperCache
Assembly: CoreWCF.NetNamedPipe
File(s): /home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Runtime/Collections/HopperCache.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 67
Coverable lines: 67
Total lines: 231
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 40
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%
Add(...)0%660%
GetValue(...)0%34340%
.ctor(...)100%110%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Runtime/Collections/HopperCache.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.Threading;
 7
 8namespace CoreWCF.Runtime.Collections
 9{
 10    // This cache works like a MruCache, but operates loosely and without locks in the mainline path.
 11    //
 12    // It consists of three 'hoppers', which are Hashtables (chosen for their nice threading characteristics - reading
 13    // doesn't require a lock).  Items enter the cache in the second hopper.  On lookups, cache hits result in the
 14    // cache entry being promoted to the first hopper.  When the first hopper is full, the third hopper is dropped,
 15    // and the first and second hoppers are shifted down, leaving an empty first hopper.  If the second hopper is
 16    // full when a new cache entry is added, the third hopper is dropped, the second hopper is shifted down, and a
 17    // new second hopper is slotted in to become the new item entrypoint.
 18    //
 19    // Items can only be added and looked up.  There's no way to remove an item besides through attrition.
 20    //
 21    // This cache has a built-in concept of weakly-referenced items (which can be enabled or disabled in the
 22    // constructor).  It needs this concept since the caller of the cache can't remove dead cache items itself.
 23    // A weak HopperCache will simply ignore dead entries.
 24    //
 25    // This structure allows cache lookups to be almost lock-free.  The only time the first hopper is written to
 26    // is when a cache entry is promoted.  Promoting a cache entry is not critical - it's ok to skip a promotion.
 27    // Only one promotion is allowed at a time.  If a second is attempted, it is skipped.  This allows promotions
 28    // to be synchronized with just an Interlocked call.
 29    //
 30    // New cache entries go into the second hopper, which requires a lock, as does shifting the hoppers down.
 31    //
 32    // The hopperSize parameter determines the size of the first hopper.  When it reaches this size, the hoppers
 33    // are shifted.  The second hopper is allowed to grow to twice this size.  This is because it needs room to get
 34    // new cache entries into the system, and the second hopper typically starts out 'full'.  Entries are never added
 35    // directly to the third hopper.
 36    //
 37    // It's a error on the part of the caller to add the same key to the cache again if it's already in the cache
 38    // with a different value.  The new value will not necessarily overwrite the old value.
 39    //
 40    // If a cache entry is about to be promoted from the third hopper, and in the mean time the third hopper has been
 41    // shifted away, an intervening GetValue for the same key might return null, even though the item is still in
 42    // the cache and a later GetValue might find it.  So it's very important never to add the same key to the cache
 43    // with two different values, even if GetValue returns null for the key in-between the first add and the second.
 44    // (If this particular behavior is a problem, it may be possible to tighten up, but it's not necessary for the
 45    // current use of HopperCache - UriPrefixTable.)
 46    internal class HopperCache
 47    {
 48        private readonly int _hopperSize;
 49        private readonly bool _weak;
 50        private Hashtable _outstandingHopper;
 51        private Hashtable _strongHopper;
 52        private Hashtable _limitedHopper;
 53        private int _promoting;
 54        private LastHolder _mruEntry;
 55
 56
 057        public HopperCache(int hopperSize, bool weak)
 58        {
 59            Fx.Assert(hopperSize > 0, "HopperCache hopperSize must be positive.");
 60
 061            _hopperSize = hopperSize;
 062            _weak = weak;
 63
 064            _outstandingHopper = new Hashtable(hopperSize * 2);
 065            _strongHopper = new Hashtable(hopperSize * 2);
 066            _limitedHopper = new Hashtable(hopperSize * 2);
 067        }
 68
 69        // Calls to Add must be synchronized.
 70        public void Add(object key, object value)
 71        {
 72            Fx.Assert(key != null, "HopperCache key cannot be null.");
 73            Fx.Assert(value != null, "HopperCache value cannot be null.");
 74
 75            // Special-case DBNull since it can never be collected.
 076            if (_weak && !ReferenceEquals(value, DBNull.Value))
 77            {
 078                value = new WeakReference(value);
 79            }
 80
 81            Fx.Assert(_strongHopper.Count <= _hopperSize * 2,
 82                "HopperCache strongHopper is bigger than it's allowed to get.");
 83
 084            if (_strongHopper.Count >= _hopperSize * 2)
 85            {
 086                Hashtable recycled = _limitedHopper;
 087                recycled.Clear();
 088                recycled.Add(key, value);
 89
 90                // The try/finally is here to make sure these happen without interruption.
 091                try { }
 92                finally
 93                {
 094                    _limitedHopper = _strongHopper;
 095                    _strongHopper = recycled;
 096                }
 97            }
 98            else
 99            {
 100                // We do nothing to prevent things from getting added multiple times.  Also may be writing over
 101                // a dead weak entry.
 0102                _strongHopper[key] = value;
 103            }
 0104        }
 105
 106        // Calls to GetValue do not need to be synchronized, but the object used to synchronize the Add calls
 107        // must be passed in.  It's sometimes used.
 108        public object GetValue(object syncObject, object key)
 109        {
 110            Fx.Assert(key != null, "Can't look up a null key.");
 111
 112            WeakReference weakRef;
 113            object value;
 114
 115            // The MruCache does this so we have to too.
 0116            LastHolder last = _mruEntry;
 0117            if (last != null && key.Equals(last.Key))
 118            {
 0119                if (_weak && (weakRef = last.Value as WeakReference) != null)
 120                {
 0121                    value = weakRef.Target;
 0122                    if (value != null)
 123                    {
 0124                        return value;
 125                    }
 0126                    _mruEntry = null;
 127                }
 128                else
 129                {
 0130                    return last.Value;
 131                }
 132            }
 133
 134            // Try the first hopper.
 0135            object origValue = _outstandingHopper[key];
 0136            value = _weak && (weakRef = origValue as WeakReference) != null ? weakRef.Target : origValue;
 0137            if (value != null)
 138            {
 0139                _mruEntry = new LastHolder(key, origValue);
 0140                return value;
 141            }
 142
 143            // Try the subsequent hoppers.
 0144            origValue = _strongHopper[key];
 0145            value = _weak && (weakRef = origValue as WeakReference) != null ? weakRef.Target : origValue;
 0146            if (value == null)
 147            {
 0148                origValue = _limitedHopper[key];
 0149                value = _weak && (weakRef = origValue as WeakReference) != null ? weakRef.Target : origValue;
 0150                if (value == null)
 151                {
 152                    // Still no value?  It's not here.
 0153                    return null;
 154                }
 155            }
 156
 0157            _mruEntry = new LastHolder(key, origValue);
 158
 159            // If we can get the promoting semaphore, move up to the outstanding hopper.
 0160            int wasPromoting = 1;
 161            try
 162            {
 0163                try { }
 164                finally
 165                {
 166                    // This is effectively a lock, which is why it uses lock semantics.  If the Interlocked call
 167                    // were 'lost', the cache wouldn't deadlock, but it would be permanently broken.
 0168                    wasPromoting = Interlocked.CompareExchange(ref _promoting, 1, 0);
 0169                }
 170
 171                // Only one thread can be inside this 'if' at a time.
 0172                if (wasPromoting == 0)
 173                {
 174                    Fx.Assert(_outstandingHopper.Count <= _hopperSize,
 175                        "HopperCache outstandingHopper is bigger than it's allowed to get.");
 176
 0177                    if (_outstandingHopper.Count >= _hopperSize)
 178                    {
 0179                        lock (syncObject)
 180                        {
 0181                            Hashtable recycled = _limitedHopper;
 0182                            recycled.Clear();
 0183                            recycled.Add(key, origValue);
 184
 185                            // The try/finally is here to make sure these happen without interruption.
 0186                            try { }
 187                            finally
 188                            {
 0189                                _limitedHopper = _strongHopper;
 0190                                _strongHopper = _outstandingHopper;
 0191                                _outstandingHopper = recycled;
 0192                            }
 193                        }
 194                    }
 195                    else
 196                    {
 197                        // It's easy for this to happen twice with the same key.
 198                        //
 199                        // It's important that no one else can be shifting the current oustandingHopper
 200                        // during this operation.  We are only allowed to modify the *current* outstandingHopper
 201                        // while holding the pseudo-lock, which would be violated if it could be shifted out from
 202                        // under us (and potentially added to by Add in a race).
 0203                        _outstandingHopper[key] = origValue;
 204                    }
 205                }
 0206            }
 207            finally
 208            {
 0209                if (wasPromoting == 0)
 210                {
 0211                    _promoting = 0;
 212                }
 0213            }
 214
 0215            return value;
 216        }
 217
 218        private class LastHolder
 219        {
 0220            internal LastHolder(object key, object value)
 221            {
 0222                Key = key;
 0223                Value = value;
 0224            }
 225
 0226            internal object Key { get; private set; }
 227
 0228            internal object Value { get; private set; }
 229        }
 230    }
 231}