Skip to content

Commit 389975e

Browse files
committed
PR nits
re .dic changes: adds "memoize[s|d]", "mibi", "canceled", and a few similar; happy to revert if disagreeable
1 parent 7776bdc commit 389975e

23 files changed

+212
-172
lines changed

eng/spellchecking_exclusions.dic

150 Bytes
Binary file not shown.

src/Libraries/Microsoft.Extensions.Caching.Hybrid/HybridCacheOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ namespace Microsoft.Extensions.Caching.Hybrid;
88
/// </summary>
99
public class HybridCacheOptions
1010
{
11+
private const int ShiftBytesToMibiBytes = 20;
12+
1113
/// <summary>
1214
/// Gets or sets the default global options to be applied to <see cref="HybridCache"/> operations; if options are
1315
/// specified at the individual call level, the non-null values are merged (with the per-call
@@ -39,6 +41,4 @@ public class HybridCacheOptions
3941
/// tags do not contain data that should not be visible in metrics systems.
4042
/// </summary>
4143
public bool ReportTagMetrics { get; set; }
42-
43-
private const int ShiftBytesToMibiBytes = 20;
4444
}

src/Libraries/Microsoft.Extensions.Caching.Hybrid/IHybridCacheBuilder.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,3 @@ public interface IHybridCacheBuilder
1515
/// </summary>
1616
IServiceCollection Services { get; }
1717
}
18-
19-
internal sealed class HybridCacheBuilder : IHybridCacheBuilder
20-
{
21-
public HybridCacheBuilder(IServiceCollection services)
22-
{
23-
Services = services;
24-
}
25-
26-
public IServiceCollection Services { get; }
27-
}

src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/BufferChunk.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,21 @@
88

99
namespace Microsoft.Extensions.Caching.Hybrid.Internal;
1010

11-
// used to convey buffer status; like ArraySegment<byte>, but Offset is always
12-
// zero, and we use the most significant bit (MSB, the sign flag) of the length
13-
// to track whether or not to recycle this value
11+
// Used to convey buffer status; like ArraySegment<byte>, but Offset is always
12+
// zero, and we use the most significant bit of the length (usually the sign flag,
13+
// but we do not need to support negative length) to track whether or not
14+
// to recycle this value.
1415
internal readonly struct BufferChunk
1516
{
16-
private const int MSB = (1 << 31);
17+
private const int FlagReturnToPool = (1 << 31);
1718

1819
private readonly int _lengthAndPoolFlag;
20+
1921
public byte[]? Array { get; } // null for default
2022

21-
public int Length => _lengthAndPoolFlag & ~MSB;
23+
public int Length => _lengthAndPoolFlag & ~FlagReturnToPool;
2224

23-
public bool ReturnToPool => (_lengthAndPoolFlag & MSB) != 0;
25+
public bool ReturnToPool => (_lengthAndPoolFlag & FlagReturnToPool) != 0;
2426

2527
public byte[] ToArray()
2628
{
@@ -33,6 +35,13 @@ public byte[] ToArray()
3335
var copy = new byte[length];
3436
Buffer.BlockCopy(Array!, 0, copy, 0, length);
3537
return copy;
38+
39+
// Note on nullability of Array; the usage here is that a non-null array
40+
// is always provided during construction, so the only null scenario is for default(BufferChunk).
41+
// Since the constructor explicitly accesses array.Length, any null array passed to the constructor
42+
// will cause an exception, even in release (the Debug.Assert only covers debug) - although in
43+
// reality we do not expect this to ever occur (internal type, usage checked, etc). In the case of
44+
// default(BufferChunk), we know that Length will be zero, which means we will hit the [] case.
3645
}
3746

3847
public BufferChunk(byte[] array)
@@ -55,7 +64,7 @@ public BufferChunk(byte[] array, int length, bool returnToPool)
5564
Debug.Assert(array is not null, "expected valid array input");
5665
Debug.Assert(length >= 0, "expected valid length");
5766
Array = array;
58-
_lengthAndPoolFlag = length | (returnToPool ? MSB : 0);
67+
_lengthAndPoolFlag = length | (returnToPool ? FlagReturnToPool : 0);
5968
Debug.Assert(ReturnToPool == returnToPool, "return-to-pool not respected");
6069
Debug.Assert(Length == length, "length not respected");
6170
}
@@ -71,12 +80,13 @@ internal void RecycleIfAppropriate()
7180
Debug.Assert(Array is null && !ReturnToPool, "expected clean slate after recycle");
7281
}
7382

83+
// get the data as a ROS; for note on null-logic of Array!, see comment in ToArray
7484
internal ReadOnlySequence<byte> AsSequence() => Length == 0 ? default : new ReadOnlySequence<byte>(Array!, 0, Length);
7585

7686
internal BufferChunk DoNotReturnToPool()
7787
{
7888
var copy = this;
79-
Unsafe.AsRef(in copy._lengthAndPoolFlag) &= ~MSB;
89+
Unsafe.AsRef(in copy._lengthAndPoolFlag) &= ~FlagReturnToPool;
8090
Debug.Assert(copy.Length == Length, "same length expected");
8191
Debug.Assert(!copy.ReturnToPool, "do not return to pool");
8292
return copy;

src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.CacheItem.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ internal abstract class CacheItem
1414
{
1515
private int _refCount = 1; // the number of pending operations against this cache item
1616

17-
// note: the ref count is the number of callers anticipating this value at any given time; initially,
17+
public abstract bool DebugIsImmutable { get; }
18+
19+
// Note: the ref count is the number of callers anticipating this value at any given time. Initially,
1820
// it is one for a simple "get the value" flow, but if another call joins with us, it'll be incremented.
19-
// if either cancels, it will get decremented, with the entire flow being cancelled if it ever becomes
20-
// zero
21-
// this counter also drives cache lifetime, with the cache itself incrementing the count by one; in the
21+
// If either cancels, it will get decremented, with the entire flow being cancelled if it ever becomes
22+
// zero.
23+
// This counter also drives cache lifetime, with the cache itself incrementing the count by one. In the
2224
// case of mutable data, cache eviction may reduce this to zero (in cooperation with any concurrent readers,
23-
// who incr/decr around their fetch), allowing safe buffer recycling
25+
// who incr/decr around their fetch), allowing safe buffer recycling.
2426

2527
internal int RefCount => Volatile.Read(ref _refCount);
2628

@@ -36,11 +38,13 @@ internal abstract class CacheItem
3638

3739
public abstract bool TryReserveBuffer(out BufferChunk buffer);
3840

39-
public abstract bool DebugIsImmutable { get; }
40-
41-
public bool Release() // returns true ONLY for the final release step
41+
/// <summary>
42+
/// Signal that the consumer is done with this item (ref-count decr).
43+
/// </summary>
44+
/// <returns>True if this is the final release.</returns>
45+
public bool Release()
4246
{
43-
var newCount = Interlocked.Decrement(ref _refCount);
47+
int newCount = Interlocked.Decrement(ref _refCount);
4448
Debug.Assert(newCount >= 0, "over-release detected");
4549
if (newCount == 0)
4650
{
@@ -54,10 +58,10 @@ internal abstract class CacheItem
5458

5559
public bool TryReserve()
5660
{
57-
// this is basically interlocked increment, but with a check against:
61+
// This is basically interlocked increment, but with a check against:
5862
// a) incrementing upwards from zero
5963
// b) overflowing *back* to zero
60-
var oldValue = Volatile.Read(ref _refCount);
64+
int oldValue = Volatile.Read(ref _refCount);
6165
do
6266
{
6367
if (oldValue is 0 or -1)

src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.Debug.cs

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ namespace Microsoft.Extensions.Caching.Hybrid.Internal;
1212

1313
internal partial class DefaultHybridCache
1414
{
15+
/// <summary>
16+
/// Auxiliary API for testing purposes, allowing confirmation of the internal state independent of the public API.
17+
/// </summary>
1518
internal bool DebugTryGetCacheItem(string key, [NotNullWhen(true)] out CacheItem? value)
1619
{
1720
if (_localCache.TryGetValue(key, out var untyped) && untyped is CacheItem typed)
@@ -46,38 +49,33 @@ internal void DebugOnlyIncrementOutstandingBuffers()
4649

4750
private partial class MutableCacheItem<T>
4851
{
49-
[Conditional("DEBUG")]
50-
internal void DebugOnlyTrackBuffer(DefaultHybridCache cache) => DebugOnlyTrackBufferCore(cache);
51-
52-
[SuppressMessage("Minor Code Smell", "S4136:Method overloads should be grouped together", Justification = "Conditional partial declaration/usage")]
53-
[SuppressMessage("Minor Code Smell", "S3251:Implementations should be provided for \"partial\" methods", Justification = "Intentional debug-only API")]
54-
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Uses instance state in debug build")]
55-
partial void DebugOnlyDecrementOutstandingBuffers();
56-
57-
[SuppressMessage("Minor Code Smell", "S4136:Method overloads should be grouped together", Justification = "Conditional partial declaration/usage")]
58-
[SuppressMessage("Minor Code Smell", "S3251:Implementations should be provided for \"partial\" methods", Justification = "Intentional debug-only API")]
59-
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Uses instance state in debug build")]
60-
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Uses parameter in debug build")]
61-
partial void DebugOnlyTrackBufferCore(DefaultHybridCache cache);
62-
6352
#if DEBUG
64-
private DefaultHybridCache? _cache; // for buffer-tracking - only enabled in DEBUG
65-
partial void DebugOnlyDecrementOutstandingBuffers()
53+
private DefaultHybridCache? _cache; // for buffer-tracking - only needed in DEBUG
54+
#endif
55+
56+
[Conditional("DEBUG")]
57+
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Instance state used in debug")]
58+
internal void DebugOnlyTrackBuffer(DefaultHybridCache cache)
6659
{
60+
#if DEBUG
61+
_cache = cache;
6762
if (_buffer.ReturnToPool)
6863
{
69-
_cache?.DebugOnlyDecrementOutstandingBuffers();
64+
_cache?.DebugOnlyIncrementOutstandingBuffers();
7065
}
66+
#endif
7167
}
7268

73-
partial void DebugOnlyTrackBufferCore(DefaultHybridCache cache)
69+
[Conditional("DEBUG")]
70+
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Instance state used in debug")]
71+
private void DebugOnlyDecrementOutstandingBuffers()
7472
{
75-
_cache = cache;
73+
#if DEBUG
7674
if (_buffer.ReturnToPool)
7775
{
78-
_cache?.DebugOnlyIncrementOutstandingBuffers();
76+
_cache?.DebugOnlyDecrementOutstandingBuffers();
7977
}
80-
}
8178
#endif
79+
}
8280
}
8381
}

src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.ImmutableCacheItem.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ internal partial class DefaultHybridCache
99
{
1010
private sealed class ImmutableCacheItem<T> : CacheItem<T> // used to hold types that do not require defensive copies
1111
{
12-
private T _value = default!; // deferred until SetValue
12+
private static ImmutableCacheItem<T>? _sharedDefault;
1313

14-
public void SetValue(T value) => _value = value;
14+
private T _value = default!; // deferred until SetValue
1515

16-
private static ImmutableCacheItem<T>? _sharedDefault;
16+
public override bool DebugIsImmutable => true;
1717

1818
// get a shared instance that passes as "reserved"; doesn't need to be 100% singleton,
1919
// but we don't want to break the reservation rules either; if we can't reserve: create new
2020
public static ImmutableCacheItem<T> GetReservedShared()
2121
{
22-
var obj = Volatile.Read(ref _sharedDefault);
22+
ImmutableCacheItem<T>? obj = Volatile.Read(ref _sharedDefault);
2323
if (obj is null || !obj.TryReserve())
2424
{
2525
obj = new();
@@ -30,6 +30,8 @@ public static ImmutableCacheItem<T> GetReservedShared()
3030
return obj;
3131
}
3232

33+
public void SetValue(T value) => _value = value;
34+
3335
public override bool TryGetValue(out T value)
3436
{
3537
value = _value;
@@ -41,7 +43,5 @@ public override bool TryReserveBuffer(out BufferChunk buffer)
4143
buffer = default;
4244
return false; // we don't have one to reserve!
4345
}
44-
45-
public override bool DebugIsImmutable => true;
4646
}
4747
}

src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.L2.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ internal ValueTask<BufferChunk> GetFromL2Async(string key, CancellationToken tok
3232
}
3333

3434
return new(GetValidPayloadSegment(pendingLegacy.Result)); // already complete
35+
3536
case CacheFeatures.BackendCache | CacheFeatures.BackendBuffers: // IBufferWriter<byte>-based
3637
var writer = RecyclableArrayBufferWriter<byte>.Create(MaximumPayloadBytes);
3738
var cache = Unsafe.As<IBufferDistributedCache>(_backendCache!); // type-checked already

src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.MutableCacheItem.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ private sealed partial class MutableCacheItem<T> : CacheItem<T> // used to hold
1010
private IHybridCacheSerializer<T> _serializer = null!; // deferred until SetValue
1111
private BufferChunk _buffer;
1212

13+
public override bool NeedsEvictionCallback => _buffer.ReturnToPool;
14+
15+
public override bool DebugIsImmutable => false;
16+
1317
public void SetValue(ref BufferChunk buffer, IHybridCacheSerializer<T> serializer)
1418
{
1519
_serializer = serializer;
@@ -27,8 +31,6 @@ public void SetValue(T value, IHybridCacheSerializer<T> serializer, int maxLengt
2731
writer.Dispose(); // no buffers left (we just detached them), but just in case of other logic
2832
}
2933

30-
public override bool NeedsEvictionCallback => _buffer.ReturnToPool;
31-
3234
public override bool TryGetValue(out T value)
3335
{
3436
// only if we haven't already burned
@@ -62,14 +64,9 @@ public override bool TryReserveBuffer(out BufferChunk buffer)
6264
return false;
6365
}
6466

65-
public override bool DebugIsImmutable => false;
66-
6767
protected override void OnFinalRelease()
6868
{
69-
#pragma warning disable S3251 // intentional: Supply an implementation for the partial method, otherwise this call will be ignored.
7069
DebugOnlyDecrementOutstandingBuffers();
71-
#pragma warning restore S3251
72-
7370
_buffer.RecycleIfAppropriate();
7471
}
7572
}

src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/DefaultHybridCache.Serialization.cs

Lines changed: 5 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,18 @@
33

44
using System;
55
using System.Collections.Concurrent;
6-
using System.ComponentModel;
76
using System.Reflection;
87
using System.Runtime.CompilerServices;
98
using Microsoft.Extensions.DependencyInjection;
109

11-
#if !(NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER)
12-
using System.Runtime.InteropServices;
13-
using System.Runtime.Serialization;
14-
#endif
15-
1610
namespace Microsoft.Extensions.Caching.Hybrid.Internal;
11+
1712
internal partial class DefaultHybridCache
1813
{
19-
// per instance cache of typed serializers; each serializer is a
14+
// Per instance cache of typed serializers; each serializer is a
2015
// IHybridCacheSerializer<T> for the corresponding Type, but we can't
2116
// know which here - and undesirable to add an artificial non-generic
22-
// IHybridCacheSerializer base that serves no other purpose
17+
// IHybridCacheSerializer base that serves no other purpose.
2318
private readonly ConcurrentDictionary<Type, object> _serializers = new();
2419

2520
internal int MaximumPayloadBytes { get; }
@@ -31,8 +26,8 @@ internal IHybridCacheSerializer<T> GetSerializer<T>()
3126

3227
static IHybridCacheSerializer<T> ResolveAndAddSerializer(DefaultHybridCache @this)
3328
{
34-
// it isn't critical that we get only one serializer instance during start-up; what matters
35-
// is that we don't get a new serializer instance *every time*
29+
// It isn't critical that we get only one serializer instance during start-up; what matters
30+
// is that we don't get a new serializer instance *every time*.
3631
var serializer = @this._services.GetService<IHybridCacheSerializer<T>>();
3732
if (serializer is null)
3833
{
@@ -56,66 +51,4 @@ static IHybridCacheSerializer<T> ResolveAndAddSerializer(DefaultHybridCache @thi
5651
return serializer;
5752
}
5853
}
59-
60-
internal static class ImmutableTypeCache<T> // lazy memoize; T doesn't change per cache instance
61-
{
62-
// note for blittable types: a pure struct will be a full copy every time - nothing shared to mutate
63-
public static readonly bool IsImmutable = (typeof(T).IsValueType && IsBlittable<T>()) || IsTypeImmutable(typeof(T));
64-
}
65-
66-
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Catch-all failure")]
67-
private static bool IsBlittable<T>() // minimize the generic portion
68-
{
69-
#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
70-
return !RuntimeHelpers.IsReferenceOrContainsReferences<T>();
71-
#else
72-
// down-level: only blittable types can be pinned
73-
try
74-
{
75-
// get a typed, zeroed, non-null boxed instance of the appropriate type
76-
// (can't use (object)default(T), as that would box to null for nullable types)
77-
var obj = FormatterServices.GetUninitializedObject(Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T));
78-
GCHandle.Alloc(obj, GCHandleType.Pinned).Free();
79-
return true;
80-
}
81-
catch
82-
{
83-
return false;
84-
}
85-
#endif
86-
}
87-
88-
[System.Diagnostics.CodeAnalysis.SuppressMessage("Blocker Code Smell", "S2178:Short-circuit logic should be used in boolean contexts",
89-
Justification = "Non-short-circuiting intentional to remove unnecessary branch")]
90-
private static bool IsTypeImmutable(Type type)
91-
{
92-
// check for known types
93-
if (type == typeof(string))
94-
{
95-
return true;
96-
}
97-
98-
if (type.IsValueType)
99-
{
100-
// switch from Foo? to Foo if necessary
101-
if (Nullable.GetUnderlyingType(type) is { } nullable)
102-
{
103-
type = nullable;
104-
}
105-
}
106-
107-
if (type.IsValueType || (type.IsClass & type.IsSealed))
108-
{
109-
// check for [ImmutableObject(true)]; note we're looking at this as a statement about
110-
// the overall nullability; for example, a type could contain a private int[] field,
111-
// where the field is mutable and the list is mutable; but if the type is annotated:
112-
// we're trusting that the API and use-case is such that the type is immutable
113-
return type.GetCustomAttribute<ImmutableObjectAttribute>() is { Immutable: true };
114-
}
115-
116-
// don't trust interfaces and non-sealed types; we might have any concrete
117-
// type that has different behaviour
118-
return false;
119-
120-
}
12154
}

0 commit comments

Comments
 (0)