| 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(); 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, }; }