Skip to content

Commit

Permalink
Merge pull request #108714 from dotnet/merge/release/6.0-to-release/6…
Browse files Browse the repository at this point in the history
….0-staging

[automated] Merge branch 'release/6.0' => 'release/6.0-staging'
  • Loading branch information
carlossanlop authored Oct 14, 2024
2 parents 53cf259 + 061c8b7 commit ed9b95f
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 52 deletions.
144 changes: 127 additions & 17 deletions src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Internal;
Expand All @@ -23,7 +25,8 @@ public class MemoryCache : IMemoryCache
internal readonly ILogger _logger;

private readonly MemoryCacheOptions _options;
private readonly ConcurrentDictionary<object, CacheEntry> _entries;
private readonly ConcurrentDictionary<string, CacheEntry> _stringKeyEntries;
private readonly ConcurrentDictionary<object, CacheEntry> _nonStringKeyEntries;

private long _cacheSize;
private bool _disposed;
Expand Down Expand Up @@ -56,7 +59,8 @@ public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor, ILoggerFactory
_options = optionsAccessor.Value;
_logger = loggerFactory.CreateLogger<MemoryCache>();

_entries = new ConcurrentDictionary<object, CacheEntry>();
_stringKeyEntries = new ConcurrentDictionary<string, CacheEntry>(StringKeyComparer.Instance);
_nonStringKeyEntries = new ConcurrentDictionary<object, CacheEntry>();

if (_options.Clock == null)
{
Expand All @@ -74,12 +78,14 @@ public MemoryCache(IOptions<MemoryCacheOptions> optionsAccessor, ILoggerFactory
/// <summary>
/// Gets the count of the current entries for diagnostic purposes.
/// </summary>
public int Count => _entries.Count;
public int Count => _stringKeyEntries.Count + _nonStringKeyEntries.Count;

// internal for testing
internal long Size { get => Interlocked.Read(ref _cacheSize); }

private ICollection<KeyValuePair<object, CacheEntry>> EntriesCollection => _entries;
private ICollection<KeyValuePair<string, CacheEntry>> StringKeyEntriesCollection => _stringKeyEntries;

private ICollection<KeyValuePair<object, CacheEntry>> NonStringKeyEntriesCollection => _nonStringKeyEntries;

/// <inheritdoc />
public ICacheEntry CreateEntry(object key)
Expand Down Expand Up @@ -129,7 +135,16 @@ internal void SetEntry(CacheEntry entry)
// Initialize the last access timestamp at the time the entry is added
entry.LastAccessed = utcNow;

if (_entries.TryGetValue(entry.Key, out CacheEntry priorEntry))
CacheEntry priorEntry = null;
string s = entry.Key as string;
if (s != null)
{
if (_stringKeyEntries.TryGetValue(s, out priorEntry))
{
priorEntry.SetExpired(EvictionReason.Replaced);
}
}
else if (_nonStringKeyEntries.TryGetValue(entry.Key, out priorEntry))
{
priorEntry.SetExpired(EvictionReason.Replaced);
}
Expand All @@ -143,12 +158,26 @@ internal void SetEntry(CacheEntry entry)
if (priorEntry == null)
{
// Try to add the new entry if no previous entries exist.
entryAdded = _entries.TryAdd(entry.Key, entry);
if (s != null)
{
entryAdded = _stringKeyEntries.TryAdd(s, entry);
}
else
{
entryAdded = _nonStringKeyEntries.TryAdd(entry.Key, entry);
}
}
else
{
// Try to update with the new entry if a previous entries exist.
entryAdded = _entries.TryUpdate(entry.Key, entry, priorEntry);
if (s != null)
{
entryAdded = _stringKeyEntries.TryUpdate(s, entry, priorEntry);
}
else
{
entryAdded = _nonStringKeyEntries.TryUpdate(entry.Key, entry, priorEntry);
}

if (entryAdded)
{
Expand All @@ -163,7 +192,14 @@ internal void SetEntry(CacheEntry entry)
// The update will fail if the previous entry was removed after retrival.
// Adding the new entry will succeed only if no entry has been added since.
// This guarantees removing an old entry does not prevent adding a new entry.
entryAdded = _entries.TryAdd(entry.Key, entry);
if (s != null)
{
entryAdded = _stringKeyEntries.TryAdd(s, entry);
}
else
{
entryAdded = _nonStringKeyEntries.TryAdd(entry.Key, entry);
}
}
}

Expand Down Expand Up @@ -223,7 +259,18 @@ public bool TryGetValue(object key, out object result)

DateTimeOffset utcNow = _options.Clock.UtcNow;

if (_entries.TryGetValue(key, out CacheEntry entry))
bool found;
CacheEntry entry;
if (key is string s)
{
found = _stringKeyEntries.TryGetValue(s, out entry);
}
else
{
found = _nonStringKeyEntries.TryGetValue(key, out entry);
}

if (found)
{
// Check if expired due to expiration tokens, timers, etc. and if so, remove it.
// Allow a stale Replaced value to be returned due to concurrent calls to SetExpired during SetEntry.
Expand Down Expand Up @@ -262,7 +309,18 @@ public void Remove(object key)
ValidateCacheKey(key);

CheckDisposed();
if (_entries.TryRemove(key, out CacheEntry entry))
bool removed;
CacheEntry entry;
if (key is string s)
{
removed = _stringKeyEntries.TryRemove(s, out entry);
}
else
{
removed = _nonStringKeyEntries.TryRemove(key, out entry);
}

if (removed)
{
if (_options.SizeLimit.HasValue)
{
Expand All @@ -278,7 +336,17 @@ public void Remove(object key)

private void RemoveEntry(CacheEntry entry)
{
if (EntriesCollection.Remove(new KeyValuePair<object, CacheEntry>(entry.Key, entry)))
bool removed;
if (entry.Key is string s)
{
removed = StringKeyEntriesCollection.Remove(new KeyValuePair<string, CacheEntry>(s, entry));
}
else
{
removed = NonStringKeyEntriesCollection.Remove(new KeyValuePair<object, CacheEntry>(entry.Key, entry));
}

if (removed)
{
if (_options.SizeLimit.HasValue)
{
Expand Down Expand Up @@ -317,10 +385,8 @@ private static void ScanForExpiredItems(MemoryCache cache)
{
DateTimeOffset now = cache._lastExpirationScan = cache._options.Clock.UtcNow;

foreach (KeyValuePair<object, CacheEntry> item in cache._entries)
foreach (CacheEntry entry in cache.GetCacheEntries())
{
CacheEntry entry = item.Value;

if (entry.CheckExpired(now))
{
cache.RemoveEntry(entry);
Expand Down Expand Up @@ -388,10 +454,26 @@ private static void OvercapacityCompaction(MemoryCache cache)
/// ?. Larger objects - estimated by object graph size, inaccurate.
public void Compact(double percentage)
{
int removalCountTarget = (int)(_entries.Count * percentage);
int removalCountTarget = (int)(Count * percentage);
Compact(removalCountTarget, _ => 1);
}

private IEnumerable<CacheEntry> GetCacheEntries()
{
// note this mimics the outgoing code in that we don't just access
// .Values, which has additional overheads; this is only used for rare
// calls - compaction, clear, etc - so the additional overhead of a
// generated enumerator is not alarming
foreach (KeyValuePair<string, CacheEntry> item in _stringKeyEntries)
{
yield return item.Value;
}
foreach (KeyValuePair<object, CacheEntry> item in _nonStringKeyEntries)
{
yield return item.Value;
}
}

private void Compact(long removalSizeTarget, Func<CacheEntry, long> computeEntrySize)
{
var entriesToRemove = new List<CacheEntry>();
Expand All @@ -403,9 +485,8 @@ private void Compact(long removalSizeTarget, Func<CacheEntry, long> computeEntry

// Sort items by expired & priority status
DateTimeOffset now = _options.Clock.UtcNow;
foreach (KeyValuePair<object, CacheEntry> item in _entries)
foreach (CacheEntry entry in GetCacheEntries())
{
CacheEntry entry = item.Value;
if (entry.CheckExpired(now))
{
entriesToRemove.Add(entry);
Expand Down Expand Up @@ -526,5 +607,34 @@ private static void ValidateCacheKey(object key)

static void Throw() => throw new ArgumentNullException(nameof(key));
}

#if NETCOREAPP
// on .NET Core, the inbuilt comparer has Marvin built in; no need to intercept
private static class StringKeyComparer
{
internal static IEqualityComparer<string> Instance => EqualityComparer<string>.Default;
}
#else
// otherwise, we need a custom comparer that manually implements Marvin
private sealed class StringKeyComparer : IEqualityComparer<string>, IEqualityComparer
{
private StringKeyComparer() { }

internal static readonly IEqualityComparer<string> Instance = new StringKeyComparer();

// special-case string keys and use Marvin hashing
public int GetHashCode(string? s) => s is null ? 0
: Marvin.ComputeHash32(MemoryMarshal.AsBytes(s.AsSpan()), Marvin.DefaultSeed);

public bool Equals(string? x, string? y)
=> string.Equals(x, y);

bool IEqualityComparer.Equals(object x, object y)
=> object.Equals(x, y);

int IEqualityComparer.GetHashCode(object obj)
=> obj is string s ? Marvin.ComputeHash32(MemoryMarshal.AsBytes(s.AsSpan()), Marvin.DefaultSeed) : 0;
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
<PackageDescription>In-memory cache implementation of Microsoft.Extensions.Caching.Memory.IMemoryCache.</PackageDescription>
<ServicingVersion>1</ServicingVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<ServicingVersion>2</ServicingVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand All @@ -15,4 +17,8 @@
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Primitives\src\Microsoft.Extensions.Primitives.csproj" />
</ItemGroup>

<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'netstandard2.1'))">
<Compile Include="$(CoreLibSharedDir)System\Marvin.cs" Link="Common\System\Marvin.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
<EnableDefaultItems>true</EnableDefaultItems>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<ServicingVersion>1</ServicingVersion>
<PackageDescription>Abstractions for reading `.deps` files.

Commonly Used Types:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,12 @@ public static Zip64ExtraField GetAndRemoveZip64Block(List<ZipGenericExtraField>
zip64Field._localHeaderOffset = null;
zip64Field._startDiskNumber = null;

List<ZipGenericExtraField> markedForDelete = new List<ZipGenericExtraField>();
bool zip64FieldFound = false;

foreach (ZipGenericExtraField ef in extraFields)
extraFields.RemoveAll(ef =>
{
if (ef.Tag == TagConstant)
{
markedForDelete.Add(ef);
if (!zip64FieldFound)
{
if (TryGetZip64BlockFromGenericExtraField(ef, readUncompressedSize, readCompressedSize,
Expand All @@ -253,24 +251,18 @@ public static Zip64ExtraField GetAndRemoveZip64Block(List<ZipGenericExtraField>
zip64FieldFound = true;
}
}
return true;
}
}

foreach (ZipGenericExtraField ef in markedForDelete)
extraFields.Remove(ef);
return false;
});

return zip64Field;
}

public static void RemoveZip64Blocks(List<ZipGenericExtraField> extraFields)
{
List<ZipGenericExtraField> markedForDelete = new List<ZipGenericExtraField>();
foreach (ZipGenericExtraField field in extraFields)
if (field.Tag == TagConstant)
markedForDelete.Add(field);

foreach (ZipGenericExtraField field in markedForDelete)
extraFields.Remove(field);
extraFields.RemoveAll(field => field.Tag == TagConstant);
}

public void WriteBlock(Stream stream)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<NoWarn>$(NoWarn);CA1847</NoWarn>
<IsPackable>true</IsPackable>
<!-- If you enable GeneratePackageOnBuild for this package and bump ServicingVersion, make sure to also bump ServicingVersion in Microsoft.Windows.Compatibility.csproj once for the next release. -->
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<ServicingVersion>0</ServicingVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<ServicingVersion>1</ServicingVersion>
<PackageDescription>Provides classes that support storage of multiple data objects in a single container.</PackageDescription>
</PropertyGroup>

Expand Down Expand Up @@ -59,4 +59,4 @@
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<Reference Include="WindowsBase" />
</ItemGroup>
</Project>
</Project>
Loading

0 comments on commit ed9b95f

Please sign in to comment.