Skip to content

Commit

Permalink
Add CacheManager.TotalCount and .EnumerateEntries, expose Cached.Key (#…
Browse files Browse the repository at this point in the history
…17)

* Add CacheManager.TotalCount, .EnumerateEntries and expose Cached.Key, there is no value in keeping it private

* Fix publishing XML documentation
  • Loading branch information
neon-sunset authored Oct 4, 2022
1 parent 1167f40 commit f2e8db6
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 11 deletions.
1 change: 1 addition & 0 deletions .github/workflows/dotnet-releaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
env:
FASTCACHE_CONSIDER_GC: true
FASTCACHE_GC_THRESHOLD: 1024
FASTCACHE_PARALLEL_EVICTION_THRESHOLD: 2048
FASTCACHE_QUICKLIST_EVICTION_INTERVAL: 00:00:01
run: |
dotnet tool install -g dotnet-releaser
Expand Down
33 changes: 30 additions & 3 deletions src/FastCache.Cached/CacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,20 @@ public static class CacheManager
private static long s_AggregatedEvictionsCount;

/// <summary>
/// Submit full eviction for specified Cached<K, V> value
/// Total atomic count of entries present in cache, including expired.
/// </summary>
/// <typeparam name="K">Cache entry key type. string, int or (int, int) for multi-key.</typeparam>
/// <typeparam name="V">Cache entry value type</typeparam>
public static int TotalCount<K, V>() where K : notnull => CacheStaticHolder<K, V>.Store.Count;

/// <summary>
/// Trigger full eviction for expired cache entries of type Cached[K, V]
/// </summary>
public static void QueueFullEviction<K, V>() where K : notnull => QueueFullEviction<K, V>(triggeredByTimer: true);

/// <summary>
/// Remove all cache entries of type Cached[K, V] from the cache
/// </summary>
public static void QueueFullClear<K, V>() where K : notnull
{
ThreadPool.QueueUserWorkItem(async static _ =>
Expand All @@ -37,11 +47,28 @@ public static void QueueFullClear<K, V>() where K : notnull
});
}

/// <summary>
/// Enumerates all not expired entries currently present in the cache.
/// Cache changes introduced from other threads may not be visible to the enumerator.
/// </summary>
/// <typeparam name="K">Cache entry key type. string, int or (int, int) for multi-key.</typeparam>
/// <typeparam name="V">Cache entry value type</typeparam>
public static IEnumerable<Cached<K, V>> EnumerateEntries<K, V>() where K : notnull
{
foreach (var (key, inner) in CacheStaticHolder<K, V>.Store)
{
if (inner.IsNotExpired())
{
yield return new(key, inner.Value, found: true);
}
}
}

/// <summary>
/// Trims cache store for a given percentage of its size. Will remove at least 1 item.
/// </summary>
/// <typeparam name="K"></typeparam>
/// <typeparam name="V"></typeparam>
/// <typeparam name="K">Cache entry key type. string, int or (int, int) for multi-key.</typeparam>
/// <typeparam name="V">Cache entry value type</typeparam>
/// <param name="percentage"></param>
/// <returns>True: trim is performed inline. False: the count to trim is above threshold and removal is queued to thread pool.</returns>
public static bool Trim<K, V>(double percentage) where K : notnull
Expand Down
21 changes: 14 additions & 7 deletions src/FastCache.Cached/Cached.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@ namespace FastCache;
[StructLayout(LayoutKind.Auto)]
public readonly record struct Cached<K, V> where K : notnull
{
private readonly K _key;
private readonly bool _found;

/// <summary>
/// Cache entry key. Either a single-argument like string, int, etc. or multi-key value as tuple like (int, int, bool).
/// </summary>
public readonly K Key;

/// <summary>
/// Cache entry value. Guaranteed to be up-to-date with millisecond accuracy as long as 'bool TryGet' conditional is checked.
/// </summary>
public readonly V Value;

/// <summary>
Expand All @@ -26,9 +33,9 @@ namespace FastCache;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Cached(K key, V value, bool found)
{
_key = key;
_found = found;

Key = key;
Value = value;
}

Expand All @@ -42,12 +49,12 @@ public V Save(V value, TimeSpan expiration)
{
var (timestamp, milliseconds) = TimeUtils.GetTimestamp(expiration);

CacheStaticHolder<K, V>.Store[_key] = new(value, timestamp);
CacheStaticHolder<K, V>.Store[Key] = new(value, timestamp);
CacheStaticHolder<K, V>.EvictionJob.ReportExpiration(milliseconds);

if (!_found)
{
CacheStaticHolder<K, V>.QuickList.Add(_key, timestamp);
CacheStaticHolder<K, V>.QuickList.Add(Key, timestamp);
}

return value;
Expand Down Expand Up @@ -81,9 +88,9 @@ public bool Update(V value)
{
var store = CacheStaticHolder<K, V>.Store;

if (_found && store.TryGetValue(_key, out var inner))
if (_found && store.TryGetValue(Key, out var inner))
{
store[_key] = new(value, inner._timestamp);
store[Key] = new(value, inner._timestamp);
return true;
}

Expand All @@ -95,7 +102,7 @@ public bool Update(V value)
/// </summary>
public void Remove()
{
CacheStaticHolder<K, V>.Store.TryRemove(_key, out _);
CacheStaticHolder<K, V>.Store.TryRemove(Key, out _);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/FastCache.Cached/FastCache.Cached.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Authors>neon-sunset</Authors>
Expand All @@ -7,6 +7,7 @@
<PackageTags>Cache;Caching;Lock-Free;Performance;MemoryCache;In-Memory;High-Load</PackageTags>
<PackageIcon>cached-small-transparent.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Description>The fastest cache library written in C# for items with set expiration time. Easy to use, thread-safe and light on memory.
Optimized to scale from dozens to millions of items. Features lock-free reads and writes, allocation-free reads and automatic eviction.
Credit to Vladimir Sadov for his implementation of NonBlocking.ConcurrentDictionary which is used as an underlying store.</Description>
Expand Down
1 change: 1 addition & 0 deletions tests/FastCache.CachedTests/FastCache.CachedTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="RangeExtensions" Version="1.2.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
Expand Down
35 changes: 35 additions & 0 deletions tests/FastCache.CachedTests/Internals/CacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using FastCache.Extensions;
using FastCache.Services;
using FastCache.Helpers;
using System.Linq;

namespace FastCache.CachedTests.Internals;

Expand All @@ -10,10 +11,44 @@ public sealed class CacheManagerTests
private record ExpiredEntry(string Value);
private record OptionallyExpiredEntry(string Value, bool IsExpired);
private record RemovableEntry(string Value);
private record TotalCountEntry(int Value);
private record EnumerableEntry();

private static readonly Random Random = new();
private static readonly TimeSpan DelayTolerance = TimeSpan.FromMilliseconds(100);

[Fact]
public void TotalCount_ReturnsCorrectValue()
{
const int expectedCount = 32768;

var entries = (0..32768)
.AsEnumerable()
.Select(i => (i, new TotalCountEntry(i)));

CachedRange<TotalCountEntry>.Save(entries, TimeSpan.MaxValue);

Assert.Equal(expectedCount, CacheManager.TotalCount<int, TotalCountEntry>());
}

[Fact]
public async Task EnumerateEntries_ReturnsCorrectValues()
{
var expired = (0..1024).AsEnumerable().ToDictionary(i => i, _ => new EnumerableEntry());
var notExpired = (1024..2048).AsEnumerable().ToDictionary(i => i, _ => new EnumerableEntry());

CachedRange<EnumerableEntry>.Save(expired.Select(kvp => (kvp.Key, kvp.Value)), DelayTolerance);
CachedRange<EnumerableEntry>.Save(notExpired.Select(kvp => (kvp.Key, kvp.Value)), DelayTolerance * 2);

await Task.Delay(DelayTolerance);

foreach (var cached in CacheManager.EnumerateEntries<int, EnumerableEntry>())
{
Assert.False(expired.ContainsKey(cached.Key));
Assert.True(notExpired.ContainsKey(cached.Key));
}
}

[Theory]
[InlineData(double.NaN)]
[InlineData(double.MinValue)]
Expand Down

0 comments on commit f2e8db6

Please sign in to comment.