Skip to content

Commit 2875d2e

Browse files
authored
Add metadata caches and package comparer (#519)
* Add metadata caches and package comparer Add a global using for ConcurrentCollections and introduce PackageCacheKeyComparer to compare (package, NuGetVersion) tuple keys case-insensitively. Add two ConcurrentDictionary caches (metadataCache and latestVersionCache) to Updater and use them to short-circuit repository reads; cache entries (including null) are populated to avoid repeated package metadata/resolution calls. * .
1 parent 19103da commit 2875d2e

6 files changed

Lines changed: 121 additions & 2 deletions

File tree

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
<Project>
33
<PropertyGroup>
4-
<Version>4.1.2</Version>
4+
<Version>4.1.3</Version>
55
<LangVersion>preview</LangVersion>
66
<NoWarn>NU1608</NoWarn>
77
<AssemblyVersion>1.0.0</AssemblyVersion>

src/PackageUpdate/GlobalUsings.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
global using System.Xml;
1+
global using System.Collections.Concurrent;
2+
global using System.Diagnostics;
3+
global using System.Xml;
24
global using System.Xml.Linq;
35
global using CommandLine;
46
global using NuGet.Common;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
sealed class PackageCacheKeyComparer :
2+
IEqualityComparer<(string Package, NuGetVersion Version)>
3+
{
4+
public static PackageCacheKeyComparer Instance { get; } = new();
5+
6+
public bool Equals((string Package, NuGetVersion Version) x, (string Package, NuGetVersion Version) y) =>
7+
string.Equals(x.Package, y.Package, StringComparison.OrdinalIgnoreCase) &&
8+
EqualityComparer<NuGetVersion>.Default.Equals(x.Version, y.Version);
9+
10+
public int GetHashCode((string Package, NuGetVersion Version) obj) =>
11+
HashCode.Combine(
12+
StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Package),
13+
EqualityComparer<NuGetVersion>.Default.GetHashCode(obj.Version));
14+
}

src/PackageUpdate/Program.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ static async Task Inner(string directory, string? package, bool build)
1515
Environment.Exit(1);
1616
}
1717

18+
var totalStopwatch = Stopwatch.StartNew();
1819
using var cache = new SourceCacheContext
1920
{
2021
RefreshMemoryCache = true
@@ -28,6 +29,8 @@ static async Task Inner(string directory, string? package, bool build)
2829
{
2930
await DotnetStarter.Shutdown();
3031
}
32+
33+
Log.Information("Completed in {Elapsed}", totalStopwatch.Elapsed);
3134
}
3235

3336
static async Task TryProcessSolution(SourceCacheContext cache, string solution, string? package, bool build)
@@ -67,7 +70,9 @@ static async Task ProcessSolution(SourceCacheContext cache, string solution, str
6770
return;
6871
}
6972

73+
var stopwatch = Stopwatch.StartNew();
7074
await Updater.Update(cache, props, package);
75+
Log.Information(" Updated in {Elapsed}", stopwatch.Elapsed);
7176

7277
if (build)
7378
{

src/PackageUpdate/Updater.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
public static class Updater
22
{
3+
static ConcurrentDictionary<(string Package, NuGetVersion Version), IPackageSearchMetadata?> metadataCache = new(PackageCacheKeyComparer.Instance);
4+
static ConcurrentDictionary<(string Package, NuGetVersion CurrentVersion), IPackageSearchMetadata?> latestVersionCache = new(PackageCacheKeyComparer.Instance);
5+
36
public static async Task Update(
47
SourceCacheContext cache,
58
string directoryPackagesPropsPath,
@@ -179,6 +182,12 @@ public static async Task Update(
179182
List<PackageSource> sources,
180183
SourceCacheContext cache)
181184
{
185+
var key = (package, currentVersion);
186+
if (latestVersionCache.TryGetValue(key, out var cached))
187+
{
188+
return cached;
189+
}
190+
182191
IPackageSearchMetadata? latestMetadata = null;
183192

184193
foreach (var source in sources)
@@ -212,6 +221,7 @@ public static async Task Update(
212221
}
213222
}
214223

224+
latestVersionCache[key] = latestMetadata;
215225
return latestMetadata;
216226
}
217227

@@ -221,6 +231,12 @@ public static async Task Update(
221231
List<PackageSource> sources,
222232
SourceCacheContext cache)
223233
{
234+
var key = (package, version);
235+
if (metadataCache.TryGetValue(key, out var cached))
236+
{
237+
return cached;
238+
}
239+
224240
foreach (var source in sources)
225241
{
226242
var (_, metadataResource) = await RepositoryReader.Read(source);
@@ -233,10 +249,12 @@ public static async Task Update(
233249

234250
if (metadata != null)
235251
{
252+
metadataCache[key] = metadata;
236253
return metadata;
237254
}
238255
}
239256

257+
metadataCache[key] = null;
240258
return null;
241259
}
242260

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
public class PackageCacheKeyComparerTests
2+
{
3+
static PackageCacheKeyComparer comparer = PackageCacheKeyComparer.Instance;
4+
5+
[Fact]
6+
public void Equals_SamePackageAndVersion()
7+
{
8+
var x = ("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"));
9+
var y = ("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"));
10+
11+
Assert.True(comparer.Equals(x, y));
12+
}
13+
14+
[Fact]
15+
public void Equals_DifferentCase()
16+
{
17+
var x = ("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"));
18+
var y = ("newtonsoft.json", NuGetVersion.Parse("13.0.1"));
19+
20+
Assert.True(comparer.Equals(x, y));
21+
}
22+
23+
[Fact]
24+
public void Equals_DifferentVersion()
25+
{
26+
var x = ("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"));
27+
var y = ("Newtonsoft.Json", NuGetVersion.Parse("12.0.1"));
28+
29+
Assert.False(comparer.Equals(x, y));
30+
}
31+
32+
[Fact]
33+
public void Equals_DifferentPackage()
34+
{
35+
var x = ("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"));
36+
var y = ("NUnit", NuGetVersion.Parse("13.0.1"));
37+
38+
Assert.False(comparer.Equals(x, y));
39+
}
40+
41+
[Fact]
42+
public void GetHashCode_SameForDifferentCase()
43+
{
44+
var x = ("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"));
45+
var y = ("newtonsoft.json", NuGetVersion.Parse("13.0.1"));
46+
47+
Assert.Equal(comparer.GetHashCode(x), comparer.GetHashCode(y));
48+
}
49+
50+
[Fact]
51+
public void GetHashCode_DifferentForDifferentVersion()
52+
{
53+
var x = ("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"));
54+
var y = ("Newtonsoft.Json", NuGetVersion.Parse("12.0.1"));
55+
56+
Assert.NotEqual(comparer.GetHashCode(x), comparer.GetHashCode(y));
57+
}
58+
59+
[Fact]
60+
public void GetHashCode_DifferentForDifferentPackage()
61+
{
62+
var x = ("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"));
63+
var y = ("NUnit", NuGetVersion.Parse("13.0.1"));
64+
65+
Assert.NotEqual(comparer.GetHashCode(x), comparer.GetHashCode(y));
66+
}
67+
68+
[Fact]
69+
public void WorksWithDictionary()
70+
{
71+
var dictionary = new Dictionary<(string Package, NuGetVersion Version), string>(comparer)
72+
{
73+
[("Newtonsoft.Json", NuGetVersion.Parse("13.0.1"))] = "first"
74+
};
75+
76+
Assert.True(dictionary.ContainsKey(("newtonsoft.json", NuGetVersion.Parse("13.0.1"))));
77+
Assert.True(dictionary.ContainsKey(("NEWTONSOFT.JSON", NuGetVersion.Parse("13.0.1"))));
78+
Assert.False(dictionary.ContainsKey(("Newtonsoft.Json", NuGetVersion.Parse("12.0.1"))));
79+
}
80+
}

0 commit comments

Comments
 (0)