Skip to content

Commit 08ad28a

Browse files
author
Bart Koelman
authored
Changed TypeLocator to use ConcurrentDictionary for its cache (#700)
* Changed TypeLocator to use ConcurrentDictionary for its cache Also made ResourceDescriptor immutable, to prevent changing the shared empty instance. * Replaced static cache instance with singleton per IoC service collection I just realized that the strange interdependencies when running unit tests sequentially (causing tests to fail in AppVeyor, but not on TravisCI or locally) may actually be caused by the global cache instance. * Nope, that did not resolve the CI build issue. But it was worth a try, though. Restored build script and re-added concurrent cache for running tests in parallel. * Empty commit to restart TravisCI
1 parent 268cc7d commit 08ad28a

File tree

7 files changed

+85
-63
lines changed

7 files changed

+85
-63
lines changed

Build.ps1

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,4 @@ Else {
6666
Write-Output "RUNNING dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision"
6767
dotnet pack .\src\JsonApiDotNetCore -c Release -o .\artifacts --version-suffix=alpha1-$revision --include-symbols
6868
CheckLastExitCode
69-
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Collections.Concurrent;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using JsonApiDotNetCore.Models;
6+
7+
namespace JsonApiDotNetCore.Graph
8+
{
9+
/// <summary>
10+
/// Used to cache and locate types, to facilitate auto-resource discovery
11+
/// </summary>
12+
internal sealed class IdentifiableTypeCache
13+
{
14+
private readonly ConcurrentDictionary<Assembly, List<ResourceDescriptor>> _typeCache = new ConcurrentDictionary<Assembly, List<ResourceDescriptor>>();
15+
16+
/// <summary>
17+
/// Get all implementations of <see cref="IIdentifiable"/> in the assembly
18+
/// </summary>
19+
public IEnumerable<ResourceDescriptor> GetIdentifiableTypes(Assembly assembly)
20+
{
21+
return _typeCache.GetOrAdd(assembly, asm => FindIdentifiableTypes(asm).ToList());
22+
}
23+
24+
private static IEnumerable<ResourceDescriptor> FindIdentifiableTypes(Assembly assembly)
25+
{
26+
foreach (var type in assembly.GetTypes())
27+
{
28+
if (TypeLocator.TryGetResourceDescriptor(type, out var descriptor))
29+
{
30+
yield return descriptor;
31+
}
32+
}
33+
}
34+
}
35+
}

src/JsonApiDotNetCore/Graph/ResourceDescriptor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ public ResourceDescriptor(Type resourceType, Type idType)
1313
public Type ResourceType { get; }
1414
public Type IdType { get; }
1515

16-
internal static ResourceDescriptor Empty => new ResourceDescriptor(null, null);
16+
internal static ResourceDescriptor Empty { get; } = new ResourceDescriptor(null, null);
1717
}
1818
}

src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class ServiceDiscoveryFacade : IServiceDiscoveryFacade
4747
};
4848
private readonly IServiceCollection _services;
4949
private readonly IResourceGraphBuilder _resourceGraphBuilder;
50+
private readonly IdentifiableTypeCache _typeCache = new IdentifiableTypeCache();
5051

5152
public ServiceDiscoveryFacade(IServiceCollection services, IResourceGraphBuilder resourceGraphBuilder)
5253
{
@@ -67,7 +68,7 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly)
6768
{
6869
AddDbContextResolvers(assembly);
6970

70-
var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly);
71+
var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly);
7172
foreach (var resourceDescriptor in resourceDescriptors)
7273
{
7374
AddResource(assembly, resourceDescriptor);
@@ -77,7 +78,6 @@ public ServiceDiscoveryFacade AddAssembly(Assembly assembly)
7778
return this;
7879
}
7980

80-
8181
public IEnumerable<Type> FindDerivedTypes(Type baseType)
8282
{
8383
return baseType.Assembly.GetTypes().Where(t =>
@@ -108,9 +108,9 @@ private void AddDbContextResolvers(Assembly assembly)
108108
/// <param name="assembly">The assembly to search for resources in.</param>
109109
public ServiceDiscoveryFacade AddResources(Assembly assembly)
110110
{
111-
var identifiables = TypeLocator.GetIdentifiableTypes(assembly);
112-
foreach (var identifiable in identifiables)
113-
AddResource(assembly, identifiable);
111+
var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly);
112+
foreach (var resourceDescriptor in resourceDescriptors)
113+
AddResource(assembly, resourceDescriptor);
114114

115115
return this;
116116
}
@@ -148,7 +148,7 @@ private void AddResourceToGraph(ResourceDescriptor identifiable)
148148
/// <param name="assembly">The assembly to search for resources in.</param>
149149
public ServiceDiscoveryFacade AddServices(Assembly assembly)
150150
{
151-
var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly);
151+
var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly);
152152
foreach (var resourceDescriptor in resourceDescriptors)
153153
{
154154
AddServices(assembly, resourceDescriptor);
@@ -170,7 +170,7 @@ private void AddServices(Assembly assembly, ResourceDescriptor resourceDescripto
170170
/// <param name="assembly">The assembly to search for resources in.</param>
171171
public ServiceDiscoveryFacade AddRepositories(Assembly assembly)
172172
{
173-
var resourceDescriptors = TypeLocator.GetIdentifiableTypes(assembly);
173+
var resourceDescriptors = _typeCache.GetIdentifiableTypes(assembly);
174174
foreach (var resourceDescriptor in resourceDescriptors)
175175
{
176176
AddRepositories(assembly, resourceDescriptor);
@@ -186,7 +186,7 @@ private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescr
186186
RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor);
187187
}
188188
}
189-
189+
190190
private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor)
191191
{
192192
if (resourceDescriptor.IdType == typeof(Guid) && interfaceType.GetTypeInfo().GenericTypeParameters.Length == 1)

src/JsonApiDotNetCore/Graph/TypeLocator.cs

+1-26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using JsonApiDotNetCore.Extensions;
1+
using JsonApiDotNetCore.Extensions;
22
using JsonApiDotNetCore.Models;
33
using System;
44
using System.Collections.Generic;
@@ -12,8 +12,6 @@ namespace JsonApiDotNetCore.Graph
1212
/// </summary>
1313
internal static class TypeLocator
1414
{
15-
private static readonly Dictionary<Assembly, List<ResourceDescriptor>> _identifiableTypeCache = new Dictionary<Assembly, List<ResourceDescriptor>>();
16-
1715
/// <summary>
1816
/// Determine whether or not this is a json:api resource by checking if it implements <see cref="IIdentifiable"/>.
1917
/// Returns the status and the resultant id type, either `(true, Type)` OR `(false, null)`
@@ -24,29 +22,6 @@ public static Type GetIdType(Type resourceType)
2422
return identifiableInterface?.GetGenericArguments()[0];
2523
}
2624

27-
/// <summary>
28-
/// Get all implementations of <see cref="IIdentifiable"/> in the assembly
29-
/// </summary>
30-
public static IEnumerable<ResourceDescriptor> GetIdentifiableTypes(Assembly assembly)
31-
=> (_identifiableTypeCache.TryGetValue(assembly, out _) == false)
32-
? FindIdentifiableTypes(assembly)
33-
: _identifiableTypeCache[assembly];
34-
35-
private static IEnumerable<ResourceDescriptor> FindIdentifiableTypes(Assembly assembly)
36-
{
37-
var descriptors = new List<ResourceDescriptor>();
38-
_identifiableTypeCache[assembly] = descriptors;
39-
40-
foreach (var type in assembly.GetTypes())
41-
{
42-
if (TryGetResourceDescriptor(type, out var descriptor))
43-
{
44-
descriptors.Add(descriptor);
45-
yield return descriptor;
46-
}
47-
}
48-
}
49-
5025
/// <summary>
5126
/// Attempts to get a descriptor of the resource type.
5227
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using JsonApiDotNetCore.Graph;
2+
using JsonApiDotNetCore.Models;
3+
using UnitTests.Internal;
4+
using Xunit;
5+
6+
namespace UnitTests.Graph
7+
{
8+
public class IdentifiableTypeCacheTests
9+
{
10+
[Fact]
11+
public void GetIdentifiableTypes_Locates_Identifiable_Resource()
12+
{
13+
// Arrange
14+
var resourceType = typeof(Model);
15+
var typeCache = new IdentifiableTypeCache();
16+
17+
// Act
18+
var results = typeCache.GetIdentifiableTypes(resourceType.Assembly);
19+
20+
// Assert
21+
Assert.Contains(results, r => r.ResourceType == resourceType);
22+
}
23+
24+
[Fact]
25+
public void GetIdentifiableTypes_Only_Contains_IIdentifiable_Types()
26+
{
27+
// Arrange
28+
var resourceType = typeof(Model);
29+
var typeCache = new IdentifiableTypeCache();
30+
31+
// Act
32+
var resourceDescriptors = typeCache.GetIdentifiableTypes(resourceType.Assembly);
33+
34+
// Assert
35+
foreach(var resourceDescriptor in resourceDescriptors)
36+
Assert.True(typeof(IIdentifiable).IsAssignableFrom(resourceDescriptor.ResourceType));
37+
}
38+
}
39+
}

test/UnitTests/Graph/TypeLocator_Tests.cs

-27
Original file line numberDiff line numberDiff line change
@@ -81,33 +81,6 @@ public void GetIdType_Correctly_Identifies_NonJsonApiResource()
8181
Assert.Equal(expectedIdType, idType);
8282
}
8383

84-
[Fact]
85-
public void GetIdentifiableTypes_Locates_Identifiable_Resource()
86-
{
87-
// Arrange
88-
var resourceType = typeof(Model);
89-
90-
// Act
91-
var results = TypeLocator.GetIdentifiableTypes(resourceType.Assembly);
92-
93-
// Assert
94-
Assert.Contains(results, r => r.ResourceType == resourceType);
95-
}
96-
97-
[Fact]
98-
public void GetIdentifiableTypes_Only_Contains_IIdentifiable_Types()
99-
{
100-
// Arrange
101-
var resourceType = typeof(Model);
102-
103-
// Act
104-
var resourceDescriptors = TypeLocator.GetIdentifiableTypes(resourceType.Assembly);
105-
106-
// Assert
107-
foreach(var resourceDescriptor in resourceDescriptors)
108-
Assert.True(typeof(IIdentifiable).IsAssignableFrom(resourceDescriptor.ResourceType));
109-
}
110-
11184
[Fact]
11285
public void TryGetResourceDescriptor_Returns_True_If_Type_Is_IIdentifiable()
11386
{

0 commit comments

Comments
 (0)