File: Internal\DefaultHybridCache.SyncLock.cs
Web Access
Project: src\src\Libraries\Microsoft.Extensions.Caching.Hybrid\Microsoft.Extensions.Caching.Hybrid.csproj (Microsoft.Extensions.Caching.Hybrid)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
namespace Microsoft.Extensions.Caching.Hybrid.Internal;
 
internal partial class DefaultHybridCache
{
    // HybridCache's stampede protection requires some level of synchronization to avoid unnecessary runs
    // of the underlying data fetch; this is *minimized* by the use of double-checked locking and
    // interlocked join (adding a new request to an existing execution), but: that would leave a race
    // condition where the *remove* step of the stampede would be in a race with the *add new* step; the
    // *add new* step is inside a lock, but we need to *remove* step to share that lock, to avoid
    // the race. We deal with that by taking the same lock during remove, but *that* means we're locking
    // on all executions.
    //
    // To minimize lock contention, we will therefore use partitioning of the lock-token, by using the
    // low 3 bits of the hash-code (which we calculate eagerly only once, so: already known). This gives
    // us a fast way to split contention by 8, almost an order-of-magnitude, which is sufficient. We *could*
    // use an array for this, but: for directness, let's inline it instead (avoiding bounds-checks,
    // an extra layer of dereferencing, and the allocation; I will acknowledge these are miniscule, but:
    // it costs us nothing to do)
 
    private readonly object _syncLock0 = new();
    private readonly object _syncLock1 = new();
    private readonly object _syncLock2 = new();
    private readonly object _syncLock3 = new();
    private readonly object _syncLock4 = new();
    private readonly object _syncLock5 = new();
    private readonly object _syncLock6 = new();
    private readonly object _syncLock7 = new();
 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S109:Magic numbers should not be used", Justification = "Trivial low 3 bits")]
    internal object GetPartitionedSyncLock(in StampedeKey key) => (key.HashCode & 0b111) switch // generate 8 partitions using the low 3 bits
    {
        0 => _syncLock0,
        1 => _syncLock1,
        2 => _syncLock2,
        3 => _syncLock3,
        4 => _syncLock4,
        5 => _syncLock5,
        6 => _syncLock6,
        _ => _syncLock7,
    };
}