Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Commit 4ac9b7d

Browse files
committed
Make validator provider cache the types it discovers (#370)
Progress on NuGet/Engineering#1190
1 parent 59e2afe commit 4ac9b7d

5 files changed

Lines changed: 104 additions & 26 deletions

File tree

src/NuGet.Services.Validation.Orchestrator/ValidatorProvider.cs

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,57 +12,85 @@ namespace NuGet.Services.Validation.Orchestrator
1212
{
1313
public class ValidatorProvider : IValidatorProvider
1414
{
15+
/// <summary>
16+
/// This is a cache of all of the <see cref="IValidator"/> and <see cref="IProcessor"/> implementations
17+
/// available.
18+
/// </summary>
19+
private static EvaluatedTypes _evaluatedTypes;
20+
private static object _evaluatedTypesLock = new object();
21+
1522
private readonly IServiceProvider _serviceProvider;
1623
private readonly ILogger<ValidatorProvider> _logger;
17-
private readonly Dictionary<string, Type> _validatorTypes;
18-
private readonly Dictionary<string, Type> _processorTypes;
1924

2025
public ValidatorProvider(IServiceProvider serviceProvider, ILogger<ValidatorProvider> logger)
2126
{
2227
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
2328
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
2429

25-
using (_logger.BeginScope("Enumerating all IValidator implementations"))
30+
InitializeEvaluatedTypes(Assembly.GetCallingAssembly());
31+
}
32+
33+
/// <summary>
34+
/// Discovers all <see cref="IValidator"/> and <see cref="IProcessor"/> types available and caches the result.
35+
/// </summary>
36+
private void InitializeEvaluatedTypes(Assembly callingAssembly)
37+
{
38+
if (_evaluatedTypes != null)
39+
{
40+
return;
41+
}
42+
43+
lock (_evaluatedTypesLock)
2644
{
27-
_logger.LogTrace("Before enumeration");
28-
IEnumerable<Type> candidateTypes = GetCandidateTypes(Assembly.GetCallingAssembly());
29-
30-
_validatorTypes = candidateTypes
31-
.Where(type => typeof(IValidator).IsAssignableFrom(type)
32-
&& type != typeof(IValidator)
33-
&& type != typeof(IProcessor))
34-
.ToDictionary(type => type.Name);
35-
36-
_processorTypes = _validatorTypes
37-
.Values
38-
.Where(IsProcessor)
39-
.ToDictionary(type => type.Name);
40-
41-
_logger.LogTrace("After enumeration, got {NumImplementations} implementations: {TypeNames}",
42-
_validatorTypes.Count,
43-
_validatorTypes.Keys);
45+
if (_evaluatedTypes != null)
46+
{
47+
return;
48+
}
49+
50+
using (_logger.BeginScope("Enumerating all IValidator implementations"))
51+
{
52+
_logger.LogTrace("Before enumeration");
53+
IEnumerable<Type> candidateTypes = GetCandidateTypes(callingAssembly);
54+
55+
var validatorTypes = candidateTypes
56+
.Where(type => typeof(IValidator).IsAssignableFrom(type)
57+
&& type != typeof(IValidator)
58+
&& type != typeof(IProcessor))
59+
.ToDictionary(type => type.Name);
60+
61+
var processorTypes = validatorTypes
62+
.Values
63+
.Where(IsProcessorType)
64+
.ToDictionary(type => type.Name);
65+
66+
_logger.LogTrace("After enumeration, got {NumImplementations} implementations: {TypeNames}",
67+
validatorTypes.Count,
68+
validatorTypes.Keys);
69+
70+
_evaluatedTypes = new EvaluatedTypes(validatorTypes, processorTypes);
71+
}
4472
}
4573
}
4674

4775
public bool IsValidator(string validatorName)
4876
{
4977
validatorName = validatorName ?? throw new ArgumentNullException(nameof(validatorName));
5078

51-
return _validatorTypes.ContainsKey(validatorName);
79+
return _evaluatedTypes.ValidatorTypes.ContainsKey(validatorName);
5280
}
5381

5482
public bool IsProcessor(string validatorName)
5583
{
5684
validatorName = validatorName ?? throw new ArgumentNullException(nameof(validatorName));
5785

58-
return _processorTypes.ContainsKey(validatorName);
86+
return _evaluatedTypes.ProcessorTypes.ContainsKey(validatorName);
5987
}
6088

6189
public IValidator GetValidator(string validatorName)
6290
{
6391
validatorName = validatorName ?? throw new ArgumentNullException(nameof(validatorName));
6492

65-
if (_validatorTypes.TryGetValue(validatorName, out Type validatorType))
93+
if (_evaluatedTypes.ValidatorTypes.TryGetValue(validatorName, out Type validatorType))
6694
{
6795
return (IValidator)_serviceProvider.GetRequiredService(validatorType);
6896
}
@@ -82,9 +110,23 @@ private static IEnumerable<Type> GetCandidateTypes(Assembly callingAssembly)
82110
return candidateTypes;
83111
}
84112

85-
private static bool IsProcessor(Type type)
113+
private static bool IsProcessorType(Type type)
86114
{
87115
return typeof(IProcessor).IsAssignableFrom(type);
88116
}
117+
118+
private class EvaluatedTypes
119+
{
120+
public EvaluatedTypes(
121+
IReadOnlyDictionary<string, Type> validatorTypes,
122+
IReadOnlyDictionary<string, Type> processorTypes)
123+
{
124+
ValidatorTypes = validatorTypes ?? throw new ArgumentNullException(nameof(validatorTypes));
125+
ProcessorTypes = processorTypes ?? throw new ArgumentNullException(nameof(validatorTypes));
126+
}
127+
128+
public IReadOnlyDictionary<string, Type> ValidatorTypes { get; }
129+
public IReadOnlyDictionary<string, Type> ProcessorTypes { get; }
130+
}
89131
}
90132
}

tests/NuGet.Services.Validation.Orchestrator.Tests/ValidationProviderFacts.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,51 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Concurrent;
6+
using System.Linq;
57
using System.Threading.Tasks;
68
using Microsoft.Extensions.Logging;
79
using Moq;
10+
using Validation.PackageSigning.Core.Tests.Support;
811
using Xunit;
12+
using Xunit.Abstractions;
913

1014
namespace NuGet.Services.Validation.Orchestrator.Tests
1115
{
1216
public class ValidationProviderFacts
1317
{
18+
public class Constructor
19+
{
20+
private readonly ITestOutputHelper _output;
21+
22+
public Constructor(ITestOutputHelper output)
23+
{
24+
_output = output ?? throw new ArgumentNullException(nameof(output));
25+
}
26+
27+
[Fact]
28+
public void CachesEvaluatedTypes()
29+
{
30+
var messages = new ConcurrentBag<string>();
31+
var serviceProvider = new Mock<IServiceProvider>();
32+
var loggerFactory = new LoggerFactory().AddXunit(_output);
33+
var innerLogger = loggerFactory.CreateLogger<ValidatorProvider>();
34+
var logger = new RecordingLogger<ValidatorProvider>(innerLogger);
35+
36+
_output.WriteLine("Initializing the first instance.");
37+
38+
var targetA = new ValidatorProvider(serviceProvider.Object, logger);
39+
var messageCountA = logger.Messages.Count;
40+
41+
_output.WriteLine("Initializing the second instance.");
42+
43+
var targetB = new ValidatorProvider(serviceProvider.Object, logger);
44+
var messageCountB = logger.Messages.Count;
45+
46+
Assert.Equal(messageCountA, messageCountB);
47+
}
48+
}
49+
1450
public class IsProcessor : BaseFacts
1551
{
1652
[Theory]

tests/Validation.PackageSigning.ExtractAndValidateSignature.Tests/Support/RecordingLogger.cs renamed to tests/Validation.PackageSigning.Core.Tests/Support/RecordingLogger.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.Collections.Generic;
66
using Microsoft.Extensions.Logging;
77

8-
namespace Validation.PackageSigning.ExtractAndValidateSignature.Tests
8+
namespace Validation.PackageSigning.Core.Tests.Support
99
{
1010
public class RecordingLogger<T> : ILogger<T>
1111
{

tests/Validation.PackageSigning.Core.Tests/Validation.PackageSigning.Core.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<Compile Include="Support\CertificateIntegrationTestFixture.cs" />
4242
<Compile Include="Support\DbSetMockFactory.cs" />
4343
<Compile Include="Support\ExtensionMethods.cs" />
44+
<Compile Include="Support\RecordingLogger.cs" />
4445
<Compile Include="Support\TestDbAsyncEnumerable.cs" />
4546
<Compile Include="Support\TestDbAsyncEnumerator.cs" />
4647
<Compile Include="Support\TestDbAsyncQueryProvider.cs" />

tests/Validation.PackageSigning.ExtractAndValidateSignature.Tests/Validation.PackageSigning.ExtractAndValidateSignature.Tests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
<Reference Include="Microsoft.CSharp" />
3636
</ItemGroup>
3737
<ItemGroup>
38-
<Compile Include="Support\RecordingLogger.cs" />
3938
<Compile Include="Support\CertificateIntegrationTestCollection.cs" />
4039
<Compile Include="Support\CertificateIntegrationTestFixture.cs" />
4140
<Compile Include="HashFacts.cs" />

0 commit comments

Comments
 (0)