Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HybridCache : implement the tag expiration feature #5785

Merged
merged 33 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5fd212d
rebase hc-tags work from dev (easier to re-branch due to drift)
mgravell Jan 9, 2025
9abc346
detect malformed unicode in tags/keys
mgravell Jan 10, 2025
886a413
avoid try/finally in unicode validation step
mgravell Jan 10, 2025
d8cc8e2
make build happy
mgravell Jan 10, 2025
c3cfd2f
happier
mgravell Jan 10, 2025
1b99172
Merge branch 'main' into marc/hc-tags3
mgravell Jan 28, 2025
0478a5d
Merge branch 'main' into marc/hc-tags3
mgravell Jan 28, 2025
9a5e4dc
normalize and test how per-entry vs global options are inherited
mgravell Jan 28, 2025
5d72360
Merge branch 'marc/hc-tags3' of https://github.com/mgravell/extension…
mgravell Jan 28, 2025
6a278fa
add loggine of rejected data
mgravell Jan 29, 2025
654173b
event log for tag invalidations
mgravell Jan 29, 2025
47a4123
make the CI overlord happy
mgravell Jan 29, 2025
c60048e
rebase hc-tags work from dev (easier to re-branch due to drift)
mgravell Jan 9, 2025
6664698
detect malformed unicode in tags/keys
mgravell Jan 10, 2025
75defc2
avoid try/finally in unicode validation step
mgravell Jan 10, 2025
ba433e5
make build happy
mgravell Jan 10, 2025
138946a
happier
mgravell Jan 10, 2025
f78e41d
normalize and test how per-entry vs global options are inherited
mgravell Jan 28, 2025
466b369
add loggine of rejected data
mgravell Jan 29, 2025
803003f
event log for tag invalidations
mgravell Jan 29, 2025
1b89029
make the CI overlord happy
mgravell Jan 29, 2025
33279db
Merge branch 'marc/hc-tags3' of https://github.com/mgravell/extension…
mgravell Jan 29, 2025
b761e50
add event-source into more tests (proves logging-related code branche…
mgravell Jan 30, 2025
11c7efd
add inbuilt type serializer test
mgravell Jan 30, 2025
16eff9e
add license header
mgravell Jan 30, 2025
437e2fa
improving code coverage
mgravell Jan 30, 2025
a5b816e
incorporate PR feedback re thread-static array
mgravell Jan 30, 2025
e470ef7
make the robots happy
mgravell Jan 30, 2025
a20dc13
Update src/Libraries/Microsoft.Extensions.Caching.Hybrid/Internal/Tag…
mgravell Jan 30, 2025
98dfbd6
tag-based invalidate: pass multiple tags, to ensure code paths
mgravell Jan 30, 2025
8b5155e
Merge branch 'marc/hc-tags3' of https://github.com/mgravell/extension…
mgravell Jan 30, 2025
6c7870b
Merge branch 'main' into marc/hc-tags3
mgravell Feb 3, 2025
07e7a39
more coverage
mgravell Feb 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/MSBuild/LegacySupport.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\CallerAttributes\*.cs" LinkBase="LegacySupport\CallerAttributes" />
</ItemGroup>

<ItemGroup Condition="'$(InjectSkipLocalsInitAttributeOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp3.1')">
<ItemGroup Condition="'$(InjectSkipLocalsInitAttributeOnLegacy)' == 'true' AND ('$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'netcoreapp3.1')">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\SkipLocalsInitAttribute\*.cs" LinkBase="LegacySupport\SkipLocalsInitAttribute" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,23 @@ namespace Microsoft.Extensions.Caching.Hybrid.Internal;
internal readonly struct BufferChunk
{
private const int FlagReturnToPool = (1 << 31);

private readonly int _lengthAndPoolFlag;

public byte[]? Array { get; } // null for default
public byte[]? OversizedArray { get; } // null for default

public bool HasValue => OversizedArray is not null;

public int Offset { get; }
public int Length => _lengthAndPoolFlag & ~FlagReturnToPool;

public bool ReturnToPool => (_lengthAndPoolFlag & FlagReturnToPool) != 0;

public BufferChunk(byte[] array)
{
Debug.Assert(array is not null, "expected valid array input");
Array = array;
OversizedArray = array;
_lengthAndPoolFlag = array!.Length;
Offset = 0;

// assume not pooled, if exact-sized
// (we don't expect array.Length to be negative; we're really just saying
Expand All @@ -39,11 +42,12 @@ public BufferChunk(byte[] array)
Debug.Assert(Length == array.Length, "array length not respected");
}

public BufferChunk(byte[] array, int length, bool returnToPool)
public BufferChunk(byte[] array, int offset, int length, bool returnToPool)
{
Debug.Assert(array is not null, "expected valid array input");
Debug.Assert(length >= 0, "expected valid length");
Array = array;
OversizedArray = array;
Offset = offset;
_lengthAndPoolFlag = length | (returnToPool ? FlagReturnToPool : 0);
Debug.Assert(ReturnToPool == returnToPool, "return-to-pool not respected");
Debug.Assert(Length == length, "length not respected");
Expand All @@ -58,7 +62,7 @@ public byte[] ToArray()
}

var copy = new byte[length];
Buffer.BlockCopy(Array!, 0, copy, 0, length);
Buffer.BlockCopy(OversizedArray!, Offset, copy, 0, length);
return copy;

// Note on nullability of Array; the usage here is that a non-null array
Expand All @@ -73,15 +77,19 @@ internal void RecycleIfAppropriate()
{
if (ReturnToPool)
{
ArrayPool<byte>.Shared.Return(Array!);
ArrayPool<byte>.Shared.Return(OversizedArray!);
}

Unsafe.AsRef(in this) = default; // anti foot-shotgun double-return guard; not 100%, but worth doing
Debug.Assert(Array is null && !ReturnToPool, "expected clean slate after recycle");
Debug.Assert(OversizedArray is null && !ReturnToPool, "expected clean slate after recycle");
}

internal ArraySegment<byte> AsArraySegment() => Length == 0 ? default! : new(OversizedArray!, Offset, Length);

internal ReadOnlySpan<byte> AsSpan() => Length == 0 ? default : new(OversizedArray!, Offset, Length);

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

internal BufferChunk DoNotReturnToPool()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
Expand All @@ -13,10 +14,22 @@ internal partial class DefaultHybridCache
{
internal abstract class CacheItem
{
private readonly long _creationTimestamp;

protected CacheItem(long creationTimestamp, TagSet tags)
{
Tags = tags;
_creationTimestamp = creationTimestamp;
}

private int _refCount = 1; // the number of pending operations against this cache item

public abstract bool DebugIsImmutable { get; }

public long CreationTimestamp => _creationTimestamp;

public TagSet Tags { get; }

// Note: the ref count is the number of callers anticipating this value at any given time. Initially,
// it is one for a simple "get the value" flow, but if another call joins with us, it'll be incremented.
// If either cancels, it will get decremented, with the entire flow being cancelled if it ever becomes
Expand All @@ -27,6 +40,9 @@ internal abstract class CacheItem

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

internal void UnsafeSetCreationTimestamp(long timestamp)
=> Unsafe.AsRef(in _creationTimestamp) = timestamp;

internal static readonly PostEvictionDelegate SharedOnEviction = static (key, value, reason, state) =>
{
if (value is CacheItem item)
Expand Down Expand Up @@ -88,6 +104,11 @@ protected virtual void OnFinalRelease() // any required release semantics

internal abstract class CacheItem<T> : CacheItem
{
protected CacheItem(long creationTimestamp, TagSet tags)
: base(creationTimestamp, tags)
{
}

public abstract bool TryGetSize(out long size);

// Attempt to get a value that was *not* previously reserved.
Expand All @@ -112,6 +133,7 @@ public T GetReservedValue(ILogger log)
static void Throw() => throw new ObjectDisposedException("The cache item has been recycled before the value was obtained");
}

internal static CacheItem<T> Create() => ImmutableTypeCache<T>.IsImmutable ? new ImmutableCacheItem<T>() : new MutableCacheItem<T>();
internal static CacheItem<T> Create(long creationTimestamp, TagSet tags) => ImmutableTypeCache<T>.IsImmutable
? new ImmutableCacheItem<T>(creationTimestamp, tags) : new MutableCacheItem<T>(creationTimestamp, tags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,13 @@ internal void DebugOnlyIncrementOutstandingBuffers()
}
#endif

private partial class MutableCacheItem<T>
internal partial class MutableCacheItem<T>
{
#if DEBUG
private DefaultHybridCache? _cache; // for buffer-tracking - only needed in DEBUG
#endif

[Conditional("DEBUG")]
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Instance state used in debug")]
internal void DebugOnlyTrackBuffer(DefaultHybridCache cache)
{
#if DEBUG
Expand All @@ -63,18 +62,21 @@ internal void DebugOnlyTrackBuffer(DefaultHybridCache cache)
{
_cache?.DebugOnlyIncrementOutstandingBuffers();
}
#else
_ = this; // dummy just to prevent CA1822, never hit
#endif
}

[Conditional("DEBUG")]
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Instance state used in debug")]
private void DebugOnlyDecrementOutstandingBuffers()
{
#if DEBUG
if (_buffer.ReturnToPool)
{
_cache?.DebugOnlyDecrementOutstandingBuffers();
}
#else
_ = this; // dummy just to prevent CA1822, never hit
#endif
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@ namespace Microsoft.Extensions.Caching.Hybrid.Internal;

internal partial class DefaultHybridCache
{
private sealed class ImmutableCacheItem<T> : CacheItem<T> // used to hold types that do not require defensive copies
internal sealed class ImmutableCacheItem<T> : CacheItem<T> // used to hold types that do not require defensive copies
{
private static ImmutableCacheItem<T>? _sharedDefault;

public ImmutableCacheItem(long creationTimestamp, TagSet tags)
: base(creationTimestamp, tags)
{
}

private T _value = default!; // deferred until SetValue

public long Size { get; private set; } = -1;
Expand All @@ -25,7 +30,7 @@ public static ImmutableCacheItem<T> GetReservedShared()
ImmutableCacheItem<T>? obj = Volatile.Read(ref _sharedDefault);
if (obj is null || !obj.TryReserve())
{
obj = new();
obj = new(0, TagSet.Empty); // timestamp doesn't matter - not used in L1/L2
_ = obj.TryReserve(); // this is reliable on a new instance
Volatile.Write(ref _sharedDefault, obj);
}
Expand Down
Loading