Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
4 changes: 2 additions & 2 deletions eng/MSBuild/LegacySupport.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\LegacySupport\IsExternalInit\*.cs" LinkBase="LegacySupport\IsExternalInit" />
</ItemGroup>

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

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

Expand Down
2 changes: 2 additions & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<!-- Packages from dotnet/runtime -->
<MicrosoftBclTimeProviderVersion>9.0.0-rc.1.24419.2</MicrosoftBclTimeProviderVersion>
<MicrosoftExtensionsCachingAbstractionsVersion>9.0.0-rc.1.24419.2</MicrosoftExtensionsCachingAbstractionsVersion>
<MicrosoftExtensionsCachingMemoryVersion>9.0.0-rc.1.24419.2</MicrosoftExtensionsCachingMemoryVersion>
<MicrosoftExtensionsConfigurationAbstractionsVersion>9.0.0-rc.1.24419.2</MicrosoftExtensionsConfigurationAbstractionsVersion>
<MicrosoftExtensionsConfigurationBinderVersion>9.0.0-rc.1.24419.2</MicrosoftExtensionsConfigurationBinderVersion>
<MicrosoftExtensionsConfigurationJsonVersion>9.0.0-rc.1.24419.2</MicrosoftExtensionsConfigurationJsonVersion>
Expand Down Expand Up @@ -64,6 +65,7 @@
<MicrosoftAspNetCoreAppRuntimewinx64Version>9.0.0-rc.2.24430.8</MicrosoftAspNetCoreAppRuntimewinx64Version>
<MicrosoftAspNetCoreMvcTestingVersion>9.0.0-rc.2.24430.8</MicrosoftAspNetCoreMvcTestingVersion>
<MicrosoftAspNetCoreTestHostVersion>9.0.0-rc.2.24430.8</MicrosoftAspNetCoreTestHostVersion>
<MicrosoftExtensionsCachingSqlServerVersion>9.0.0-rc.2.24430.8</MicrosoftExtensionsCachingSqlServerVersion>
<MicrosoftExtensionsCachingStackExchangeRedisVersion>9.0.0-rc.2.24430.8</MicrosoftExtensionsCachingStackExchangeRedisVersion>
<MicrosoftExtensionsDiagnosticsHealthChecksVersion>9.0.0-rc.2.24430.8</MicrosoftExtensionsDiagnosticsHealthChecksVersion>
<MicrosoftExtensionsHttpPollyVersion>9.0.0-rc.2.24430.8</MicrosoftExtensionsHttpPollyVersion>
Expand Down
3 changes: 3 additions & 0 deletions eng/packages/General.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.4.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="$(MicrosoftExtensionsCachingAbstractionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="$(MicrosoftExtensionsCachingMemoryVersion)" />
<PackageVersion Include="Microsoft.Extensions.Caching.SqlServer" Version="$(MicrosoftExtensionsCachingSqlServerVersion)" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="$(MicrosoftExtensionsCachingStackExchangeRedisVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="$(MicrosoftExtensionsConfigurationAbstractionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsConfigurationBinderVersion)" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonVersion)" />
Expand Down
Binary file modified eng/spellchecking_exclusions.dic
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Configuration extension methods for <see cref="IHybridCacheBuilder"/> / <see cref="HybridCache"/>.
/// </summary>
public static class HybridCacheBuilderExtensions
{
/// <summary>
/// Serialize values of type <typeparamref name="T"/> with the specified serializer from <paramref name="serializer"/>.
/// </summary>
/// <typeparam name="T">The type to be serialized.</typeparam>
/// <returns>The <see cref="IHybridCacheBuilder"/> instance.</returns>
public static IHybridCacheBuilder AddSerializer<T>(this IHybridCacheBuilder builder, IHybridCacheSerializer<T> serializer)
{
_ = Throw.IfNull(builder).Services.AddSingleton<IHybridCacheSerializer<T>>(serializer);
return builder;
}

/// <summary>
/// Serialize values of type <typeparamref name="T"/> with the serializer of type <typeparamref name="TImplementation"/>.
/// </summary>
/// <typeparam name="T">The type to be serialized.</typeparam>
/// <typeparam name="TImplementation">The serializer to use for this type.</typeparam>
/// <returns>The <see cref="IHybridCacheBuilder"/> instance.</returns>
public static IHybridCacheBuilder AddSerializer<T,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this IHybridCacheBuilder builder)
where TImplementation : class, IHybridCacheSerializer<T>
{
_ = Throw.IfNull(builder).Services.AddSingleton<IHybridCacheSerializer<T>, TImplementation>();
return builder;
}

/// <summary>
/// Add <paramref name="factory"/> as an additional serializer factory, which can provide serializers for multiple types.
/// </summary>
/// <returns>The <see cref="IHybridCacheBuilder"/> instance.</returns>
public static IHybridCacheBuilder AddSerializerFactory(this IHybridCacheBuilder builder, IHybridCacheSerializerFactory factory)
{
_ = Throw.IfNull(builder).Services.AddSingleton<IHybridCacheSerializerFactory>(factory);
return builder;
}

/// <summary>
/// Add a factory of type <typeparamref name="TImplementation"/> as an additional serializer factory, which can provide serializers for multiple types.
/// </summary>
/// <typeparam name="TImplementation">The type of the serializer factory.</typeparam>
/// <returns>The <see cref="IHybridCacheBuilder"/> instance.</returns>
public static IHybridCacheBuilder AddSerializerFactory<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this IHybridCacheBuilder builder)
where TImplementation : class, IHybridCacheSerializerFactory
{
_ = Throw.IfNull(builder).Services.AddSingleton<IHybridCacheSerializerFactory, TImplementation>();
return builder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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;

/// <summary>
/// Options for configuring the default <see cref="HybridCache"/> implementation.
/// </summary>
public class HybridCacheOptions
{
private const int ShiftBytesToMibiBytes = 20;

/// <summary>
/// Gets or sets the default global options to be applied to <see cref="HybridCache"/> operations; if options are
/// specified at the individual call level, the non-null values are merged (with the per-call
/// options being used in preference to the global options). If no value is specified for a given
/// option (globally or per-call), the implementation may choose a reasonable default.
/// </summary>
public HybridCacheEntryOptions? DefaultEntryOptions { get; set; }

/// <summary>
/// Gets or sets a value indicating whether compression for this <see cref="HybridCache"/> instance is disabled.
/// </summary>
public bool DisableCompression { get; set; }

/// <summary>
/// Gets or sets the maximum size of cache items; attempts to store values over this size will be logged
/// and the value will not be stored in cache.
/// </summary>
/// <remarks>The default value is 1 MiB.</remarks>
public long MaximumPayloadBytes { get; set; } = 1 << ShiftBytesToMibiBytes; // 1MiB

/// <summary>
/// Gets or sets the maximum permitted length (in characters) of keys; attempts to use keys over this size will be logged.
/// </summary>
/// <remark>The default value is 1024 characters.</remark>
public int MaximumKeyLength { get; set; } = 1024; // characters

/// <summary>
/// Gets or sets a value indicating whether to use "tags" data as dimensions on metric reporting; if enabled, care should be used to ensure that
/// tags do not contain data that should not be visible in metrics systems.
/// </summary>
public bool ReportTagMetrics { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.Extensions.Caching.Hybrid;
using Microsoft.Extensions.Caching.Hybrid.Internal;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Configuration extension methods for <see cref="HybridCache"/>.
/// </summary>
public static class HybridCacheServiceExtensions
{
/// <summary>
/// Adds support for multi-tier caching services.
/// </summary>
/// <returns>A builder instance that allows further configuration of the <see cref="HybridCache"/> system.</returns>
public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services, Action<HybridCacheOptions> setupAction)
{
_ = Throw.IfNull(setupAction);
_ = AddHybridCache(services);
_ = services.Configure(setupAction);
return new HybridCacheBuilder(services);
}

/// <summary>
/// Adds support for multi-tier caching services.
/// </summary>
/// <returns>A builder instance that allows further configuration of the <see cref="HybridCache"/> system.</returns>
public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services)
{
_ = Throw.IfNull(services);
services.TryAddSingleton(TimeProvider.System);
_ = services.AddOptions().AddMemoryCache();
services.TryAddSingleton<IHybridCacheSerializerFactory, DefaultJsonSerializerFactory>();
services.TryAddSingleton<IHybridCacheSerializer<string>>(InbuiltTypeSerializer.Instance);
services.TryAddSingleton<IHybridCacheSerializer<byte[]>>(InbuiltTypeSerializer.Instance);
services.TryAddSingleton<HybridCache, DefaultHybridCache>();
return new HybridCacheBuilder(services);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Caching.Hybrid;

/// <summary>
/// Helper API for configuring <see cref="HybridCache"/>.
/// </summary>
public interface IHybridCacheBuilder
{
/// <summary>
/// Gets the services collection associated with this instance.
/// </summary>
IServiceCollection Services { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace Microsoft.Extensions.Caching.Hybrid.Internal;

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

private readonly int _lengthAndPoolFlag;

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

public int Length => _lengthAndPoolFlag & ~FlagReturnToPool;

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

public byte[] ToArray()
{
var length = Length;
if (length == 0)
{
return [];
}

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

// Note on nullability of Array; the usage here is that a non-null array
// is always provided during construction, so the only null scenario is for default(BufferChunk).
// Since the constructor explicitly accesses array.Length, any null array passed to the constructor
// will cause an exception, even in release (the Debug.Assert only covers debug) - although in
// reality we do not expect this to ever occur (internal type, usage checked, etc). In the case of
// default(BufferChunk), we know that Length will be zero, which means we will hit the [] case.
}

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

// assume not pooled, if exact-sized
// (we don't expect array.Length to be negative; we're really just saying
// "we expect the result of assigning array.Length to _lengthAndPoolFlag
// to give the expected Length *and* not have the MSB set; we're just
// checking that we haven't fat-fingered our MSB logic)
Debug.Assert(!ReturnToPool, "do not return right-sized arrays");
Debug.Assert(Length == array.Length, "array length not respected");
}

public BufferChunk(byte[] array, int length, bool returnToPool)
{
Debug.Assert(array is not null, "expected valid array input");
Debug.Assert(length >= 0, "expected valid length");
Array = array;
_lengthAndPoolFlag = length | (returnToPool ? FlagReturnToPool : 0);
Debug.Assert(ReturnToPool == returnToPool, "return-to-pool not respected");
Debug.Assert(Length == length, "length not respected");
}

internal void RecycleIfAppropriate()
{
if (ReturnToPool)
{
ArrayPool<byte>.Shared.Return(Array!);
}

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");
}

// 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 BufferChunk DoNotReturnToPool()
{
var copy = this;
Unsafe.AsRef(in copy._lengthAndPoolFlag) &= ~FlagReturnToPool;
Debug.Assert(copy.Length == Length, "same length expected");
Debug.Assert(!copy.ReturnToPool, "do not return to pool");
return copy;
}
}
Loading