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

Merging internal commits for release/6.0 #108675

Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading