Skip to content

Commit d1f2830

Browse files
authored
Moved cache into separate caching package that is AOT compatible (#8255)
1 parent 5029893 commit d1f2830

19 files changed

+177
-85
lines changed

src/All.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@
6767
<Folder Name="/HotChocolate/Caching/" />
6868
<Folder Name="/HotChocolate/Caching/src/">
6969
<Project Path="HotChocolate/Caching/src/Caching/HotChocolate.Caching.csproj" />
70+
<Project Path="HotChocolate/Caching/src/Caching.Memory/HotChocolate.Caching.Memory.csproj" />
7071
</Folder>
7172
<Folder Name="/HotChocolate/Caching/test/">
7273
<Project Path="HotChocolate/Caching/test/Caching.Tests/HotChocolate.Caching.Tests.csproj" />
74+
<Project Path="HotChocolate/Caching/test/Caching.Memory.Tests/HotChocolate.Caching.Memory.Tests.csproj" />
7375
</Folder>
7476
<Folder Name="/HotChocolate/Core/" />
7577
<Folder Name="/HotChocolate/Core/src/">

src/HotChocolate/Caching/HotChocolate.Caching.sln

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Solution>
2+
<Folder Name="/src/">
3+
<Project Path="src/Caching/HotChocolate.Caching.csproj" />
4+
</Folder>
5+
<Folder Name="/src/Caching.Memory/">
6+
<Project Path="src/Caching.Memory/HotChocolate.Caching.Memory.csproj" />
7+
</Folder>
8+
<Folder Name="/test/">
9+
<Project Path="test/Caching.Tests/HotChocolate.Caching.Tests.csproj" />
10+
</Folder>
11+
<Folder Name="/test/Caching.Memory.Tests/">
12+
<Project Path="test/Caching.Memory.Tests/HotChocolate.Caching.Memory.Tests.csproj" />
13+
</Folder>
14+
</Solution>

src/HotChocolate/Utilities/src/Utilities/Cache.cs renamed to src/HotChocolate/Caching/src/Caching.Memory/Cache.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
using System.Collections.Concurrent;
2-
using System.Diagnostics.Metrics;
32

4-
namespace HotChocolate.Utilities;
3+
namespace HotChocolate.Caching.Memory;
54

65
/// <summary>
76
/// The core cache implementation for the HotChocolate library.
87
/// This cache uses a ring buffer to evict the least recently
98
/// used entries when the cache is full.
109
/// https://en.wikipedia.org/wiki/Page_replacement_algorithm#Clock
1110
/// </summary>
12-
/// <typeparam name="TValue"></typeparam>
11+
/// <typeparam name="TValue">
12+
/// The type of the value that is stored in the cache.
13+
/// </typeparam>
1314
public sealed class Cache<TValue>
1415
{
1516
private readonly int _capacity;
@@ -36,7 +37,7 @@ public sealed class Cache<TValue>
3637
/// </exception>
3738
public Cache(int capacity = 256, CacheDiagnostics? diagnostics = null)
3839
{
39-
ArgumentOutOfRangeException.ThrowIfLessThan(capacity, 10);
40+
ArgumentOutOfRangeException.ThrowIfLessThan(capacity, 1);
4041

4142
_capacity = capacity;
4243
_ring = new CacheEntry[capacity];
@@ -232,14 +233,6 @@ private CacheEntry InsertNew(string key, TValue value)
232233
}
233234
}
234235

235-
/// <summary>
236-
/// Clears all entries from the cache.
237-
/// The clear might leave the cache in a dirty state.
238-
/// This is acceptable as we are not using a clear in production.
239-
/// It's more a helper for testing.
240-
/// </summary>
241-
public void Clear() => _map.Clear();
242-
243236
/// <summary>
244237
/// Returns all keys in the cache. This method is for testing only.
245238
/// </summary>

src/HotChocolate/Utilities/src/Utilities/CacheDiagnostics.cs renamed to src/HotChocolate/Caching/src/Caching.Memory/CacheDiagnostics.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace HotChocolate.Utilities;
1+
namespace HotChocolate.Caching.Memory;
22

33
/// <summary>
44
/// The diagnostics for the cache.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<PackageId>HotChocolate.Caching.Memory</PackageId>
5+
<AssemblyName>HotChocolate.Caching.Memory</AssemblyName>
6+
<RootNamespace>HotChocolate.Caching.Memory</RootNamespace>
7+
<NoWarn>HC8001;$(NoWarn)</NoWarn>
8+
</PropertyGroup>
9+
10+
<PropertyGroup>
11+
<ImplicitUsings>enable</ImplicitUsings>
12+
<Nullable>enable</Nullable>
13+
<IsAotCompatible>true</IsAotCompatible>
14+
<NoWarn>$(NoWarn);CA1812</NoWarn>
15+
</PropertyGroup>
16+
17+
<ItemGroup>
18+
<InternalsVisibleTo Include="HotChocolate.Caching.Memory.Tests" />
19+
</ItemGroup>
20+
21+
</Project>

src/HotChocolate/Utilities/src/Utilities/NoOpCacheDiagnostics.cs renamed to src/HotChocolate/Caching/src/Caching.Memory/NoOpCacheDiagnostics.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace HotChocolate.Utilities;
1+
namespace HotChocolate.Caching.Memory;
22

33
internal sealed class NoOpCacheDiagnostics : CacheDiagnostics
44
{

src/HotChocolate/Caching/src/Caching/HotChocolate.Caching.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<ItemGroup>
4-
<ProjectReference Include="..\..\..\Core\src\Execution\HotChocolate.Execution.csproj" />
5-
<ProjectReference Include="..\..\..\Core\src\Types\HotChocolate.Types.csproj" />
6-
</ItemGroup>
7-
83
<PropertyGroup>
94
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
105
</PropertyGroup>
@@ -22,6 +17,11 @@
2217
<InternalsVisibleTo Include="HotChocolate.Caching.Http.Tests" />
2318
</ItemGroup>
2419

20+
<ItemGroup>
21+
<ProjectReference Include="..\..\..\Core\src\Execution\HotChocolate.Execution.csproj" />
22+
<ProjectReference Include="..\..\..\Core\src\Types\HotChocolate.Types.csproj" />
23+
</ItemGroup>
24+
2525
<ItemGroup>
2626
<PackageReference Include="Microsoft.Net.Http.Headers" />
2727
</ItemGroup>

src/HotChocolate/Utilities/test/Utilities.Tests/CacheTests.cs renamed to src/HotChocolate/Caching/test/Caching.Memory.Tests/CacheTests.cs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using Xunit;
22

3-
namespace HotChocolate.Utilities;
3+
namespace HotChocolate.Caching.Memory;
44

55
public class CacheTests
66
{
@@ -117,4 +117,100 @@ public void Evict_Items_That_Were_Not_Recently_Access_When_Cache_Is_Full()
117117
key => Assert.Equal("i", key),
118118
key => Assert.Equal("j", key));
119119
}
120+
121+
[Fact]
122+
public void TryGet_Hit_IncrementsHitCounter()
123+
{
124+
var diag = new TestDiagnostics();
125+
var cache = new Cache<int>(capacity: 16, diagnostics: diag);
126+
127+
cache.GetOrCreate("a", _ => 42); // first call = miss
128+
Assert.True(cache.TryGet("a", out _)); // second call = hit
129+
130+
Assert.Equal(1, diag.Hits);
131+
Assert.Equal(1, diag.Misses);
132+
Assert.Equal(0, diag.Evictions);
133+
}
134+
135+
[Fact]
136+
public void GetOrCreate_HitVsMiss_CountsCorrectly()
137+
{
138+
var diag = new TestDiagnostics();
139+
var cache = new Cache<int>(capacity: 8, diagnostics: diag);
140+
141+
// first request → miss
142+
var v1 = cache.GetOrCreate("x", _ => 123);
143+
// second request → hit
144+
var v2 = cache.GetOrCreate("x", _ => 456);
145+
146+
Assert.Equal(123, v1);
147+
Assert.Equal(123, v2); // the factory isn't called second time
148+
149+
Assert.Equal(1, diag.Misses);
150+
Assert.Equal(1, diag.Hits);
151+
}
152+
153+
[Fact]
154+
public void Eviction_IsRecorded_WhenRingIsFull()
155+
{
156+
var diag = new TestDiagnostics();
157+
var cache = new Cache<int>(capacity: 2, diagnostics: diag);
158+
159+
cache.GetOrCreate("a", _ => 1); // fill slot 0
160+
cache.GetOrCreate("b", _ => 2); // fill slot 1
161+
cache.GetOrCreate("c", _ => 3); // forces eviction of one entry
162+
163+
Assert.Equal(1, diag.Evictions);
164+
Assert.Equal(2, cache.Count); // still limited by capacity
165+
}
166+
167+
[Fact]
168+
public void Gauges_ReportSizeAndCapacity()
169+
{
170+
var diag = new TestDiagnostics();
171+
var cache = new Cache<string>(capacity: 4, diagnostics: diag);
172+
173+
cache.GetOrCreate("k1", _ => "x");
174+
cache.GetOrCreate("k2", _ => "y");
175+
176+
Assert.NotNull(diag.SizeGauge);
177+
Assert.NotNull(diag.CapacityGauge);
178+
179+
Assert.Equal(2, diag.SizeGauge!());
180+
Assert.Equal(4, diag.CapacityGauge!());
181+
}
182+
183+
[Fact]
184+
public void TryGet_ReturnsFalse_WhenKeyIsAbsent()
185+
{
186+
var diag = new TestDiagnostics();
187+
var cache = new Cache<int>(capacity: 8, diagnostics: diag);
188+
189+
var found = cache.TryGet("unknown", out _);
190+
191+
Assert.False(found);
192+
Assert.Equal(1, diag.Misses); // miss path recorded
193+
Assert.Equal(0, diag.Hits);
194+
}
195+
196+
private sealed class TestDiagnostics : CacheDiagnostics
197+
{
198+
public int Hits;
199+
public int Misses;
200+
public int Evictions;
201+
public Func<long>? SizeGauge;
202+
public Func<long>? CapacityGauge;
203+
204+
public override void Hit() => Hits++;
205+
206+
public override void Miss() => Misses++;
207+
208+
public override void Evict() => Evictions++;
209+
210+
public override void RegisterSizeGauge(Func<long> sizeProvider)
211+
=> SizeGauge = sizeProvider;
212+
213+
public override void RegisterCapacityGauge(Func<long> capacityProvider)
214+
=> CapacityGauge = capacityProvider;
215+
}
120216
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="Current">
2+
3+
<PropertyGroup>
4+
<AssemblyName>HotChocolate.Caching.Memory.Tests</AssemblyName>
5+
<RootNamespace>HotChocolate.Caching.Memory</RootNamespace>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<None Update="$(MSBuildProjectDirectory)\__resources__\*.*">
10+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
11+
</None>
12+
<None Update="$(MSBuildProjectDirectory)\xunit.runner.json">
13+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
14+
</None>
15+
</ItemGroup>
16+
17+
<ItemGroup>
18+
<ProjectReference Include="..\..\..\Core\src\Types.OffsetPagination\HotChocolate.Types.OffsetPagination.csproj" />
19+
<ProjectReference Include="..\..\src\Caching.Memory\HotChocolate.Caching.Memory.csproj" />
20+
<ProjectReference Include="..\..\src\Caching\HotChocolate.Caching.csproj" />
21+
<ProjectReference Include="..\..\..\AspNetCore\src\AspNetCore\HotChocolate.AspNetCore.csproj" />
22+
<ProjectReference Include="..\..\..\Core\test\Utilities\HotChocolate.Tests.Utilities.csproj" />
23+
</ItemGroup>
24+
25+
</Project>

src/HotChocolate/Core/src/Authorization/AuthorizationCache.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System.Diagnostics.CodeAnalysis;
2-
using HotChocolate.Utilities;
2+
using HotChocolate.Caching.Memory;
33

44
namespace HotChocolate.Authorization;
55

@@ -16,7 +16,4 @@ public bool TryGetDirectives(string documentId, [NotNullWhen(true)] out Authoriz
1616

1717
public void TryAddDirectives(string documentId, AuthorizeDirective[] directives)
1818
=> _cache.GetOrCreate(documentId, static (_, d) => d, directives);
19-
20-
public void Clear()
21-
=> _cache.Clear();
2219
}

src/HotChocolate/Core/src/Execution/Caching/DefaultDocumentCache.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using HotChocolate.Caching.Memory;
23
using HotChocolate.Language;
3-
using HotChocolate.Utilities;
44

55
namespace HotChocolate.Execution.Caching;
66

@@ -17,6 +17,4 @@ public void TryAddDocument(string documentId, CachedDocument document)
1717

1818
public bool TryGetDocument(string documentId, [NotNullWhen(true)] out CachedDocument? document)
1919
=> _cache.TryGet(documentId, out document);
20-
21-
public void Clear() => _cache.Clear();
2220
}

src/HotChocolate/Core/src/Execution/Caching/DefaultPreparedOperationCache.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using HotChocolate.Caching.Memory;
23
using HotChocolate.Execution.Processing;
3-
using HotChocolate.Utilities;
44

55
namespace HotChocolate.Execution.Caching;
66

@@ -17,6 +17,4 @@ public void TryAddOperation(string operationId, IOperation operation)
1717

1818
public bool TryGetOperation(string operationId, [NotNullWhen(true)] out IOperation? operation)
1919
=> _cache.TryGet(operationId, out operation);
20-
21-
public void Clear() => _cache.Clear();
2220
}

src/HotChocolate/Core/src/Execution/Caching/IPreparedOperationCache.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,4 @@ public interface IPreparedOperationCache
4545
/// The operation that shall be cached.
4646
/// </param>
4747
void TryAddOperation(string operationId, IOperation operation);
48-
49-
/// <summary>
50-
/// Clears all items from the cache.
51-
/// </summary>
52-
void Clear();
5348
}

src/HotChocolate/Core/src/Execution/HotChocolate.Execution.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
</ItemGroup>
2929

3030
<ItemGroup>
31+
<ProjectReference Include="..\..\..\Caching\src\Caching.Memory\HotChocolate.Caching.Memory.csproj" />
3132
<ProjectReference Include="..\..\..\Utilities\src\Utilities.DependencyInjection\HotChocolate.Utilities.DependencyInjection.csproj" />
3233
<ProjectReference Include="..\Abstractions\HotChocolate.Abstractions.csproj" />
3334
<ProjectReference Include="..\Execution.Abstractions\HotChocolate.Execution.Abstractions.csproj" />

src/HotChocolate/CostAnalysis/src/CostAnalysis/Caching/DefaultCostMetricsCache.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using HotChocolate.Caching.Memory;
23
using HotChocolate.Utilities;
34

45
namespace HotChocolate.CostAnalysis.Caching;
@@ -18,6 +19,4 @@ public bool TryGetCostMetrics(
1819

1920
public void TryAddCostMetrics(string operationId, CostMetrics costMetrics)
2021
=> _cache.GetOrCreate(operationId, static (_, m) => m, costMetrics);
21-
22-
public void Clear() => _cache.Clear();
2322
}

src/HotChocolate/CostAnalysis/src/CostAnalysis/Caching/ICostMetricsCache.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,4 @@ bool TryGetCostMetrics(
4545
void TryAddCostMetrics(
4646
string operationId,
4747
CostMetrics costMetrics);
48-
49-
/// <summary>
50-
/// Clears all items from the cache.
51-
/// </summary>
52-
void Clear();
5348
}

src/HotChocolate/CostAnalysis/test/CostAnalysis.Tests/Doubles/FakeCostMetricsCache.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using HotChocolate.Caching.Memory;
23
using HotChocolate.CostAnalysis.Caching;
34
using HotChocolate.Utilities;
45

@@ -39,6 +40,4 @@ public void TryAddCostMetrics(string operationId, CostMetrics costMetrics)
3940
_cache.GetOrCreate(operationId, static (_, m) => m, costMetrics);
4041
Additions++;
4142
}
42-
43-
public void Clear() => _cache.Clear();
4443
}

0 commit comments

Comments
 (0)