Skip to content

Commit 4647962

Browse files
committed
Simplify circular dependency detection caching.
1 parent 4761a42 commit 4647962

4 files changed

Lines changed: 145 additions & 76 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using System.Runtime.CompilerServices;
6+
using Autofac.Core.Resolving.Pipeline;
7+
8+
namespace Autofac.Core.Resolving;
9+
10+
/// <summary>
11+
/// Tracks registrations encountered in a resolve operation to accelerate circular dependency checks.
12+
/// </summary>
13+
internal sealed class DependencyDetectionState
14+
{
15+
private static readonly IEqualityComparer<IComponentRegistration> ReferenceComparer = new ComponentRegistrationReferenceComparer();
16+
private readonly Dictionary<IComponentRegistration, int> _registrations = new(ReferenceComparer);
17+
18+
/// <summary>
19+
/// Gets a value indicating whether the state has been initialized from the active request stack.
20+
/// </summary>
21+
public bool IsInitialized { get; private set; }
22+
23+
/// <summary>
24+
/// Initializes the state from the existing request stack, if not already initialized.
25+
/// </summary>
26+
/// <param name="existingStack">The current request stack segment.</param>
27+
public void Initialize(IEnumerable<ResolveRequestContext> existingStack)
28+
{
29+
if (IsInitialized)
30+
{
31+
return;
32+
}
33+
34+
foreach (var context in existingStack)
35+
{
36+
AddRegistration(context.Registration);
37+
}
38+
39+
IsInitialized = true;
40+
}
41+
42+
/// <summary>
43+
/// Returns true if the registration is currently tracked.
44+
/// </summary>
45+
/// <param name="registration">The registration to check.</param>
46+
/// <returns>True if the registration is tracked.</returns>
47+
public bool Contains(IComponentRegistration registration)
48+
{
49+
return IsInitialized && _registrations.ContainsKey(registration);
50+
}
51+
52+
/// <summary>
53+
/// Adds a registration to the tracked set.
54+
/// </summary>
55+
/// <param name="registration">The registration to add.</param>
56+
public void Push(IComponentRegistration registration)
57+
{
58+
if (!IsInitialized)
59+
{
60+
return;
61+
}
62+
63+
AddRegistration(registration);
64+
}
65+
66+
/// <summary>
67+
/// Removes a registration from the tracked set.
68+
/// </summary>
69+
/// <param name="registration">The registration to remove.</param>
70+
public void Pop(IComponentRegistration registration)
71+
{
72+
if (!IsInitialized || !_registrations.TryGetValue(registration, out var count))
73+
{
74+
return;
75+
}
76+
77+
if (count <= 1)
78+
{
79+
_registrations.Remove(registration);
80+
}
81+
else
82+
{
83+
_registrations[registration] = count - 1;
84+
}
85+
}
86+
87+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
88+
private void AddRegistration(IComponentRegistration registration)
89+
{
90+
if (_registrations.TryGetValue(registration, out var count))
91+
{
92+
_registrations[registration] = count + 1;
93+
}
94+
else
95+
{
96+
_registrations.Add(registration, 1);
97+
}
98+
}
99+
100+
private sealed class ComponentRegistrationReferenceComparer : IEqualityComparer<IComponentRegistration>
101+
{
102+
public bool Equals(IComponentRegistration? x, IComponentRegistration? y) => ReferenceEquals(x, y);
103+
104+
public int GetHashCode(IComponentRegistration obj) => RuntimeHelpers.GetHashCode(obj);
105+
}
106+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace Autofac.Core.Resolving;
5+
6+
/// <summary>
7+
/// Provides a place to store dependency detection state for a resolve operation.
8+
/// </summary>
9+
internal interface IDependencyDetectionStateOwner
10+
{
11+
/// <summary>
12+
/// Gets or sets the cached dependency detection state.
13+
/// </summary>
14+
DependencyDetectionState? DependencyDetectionState { get; set; }
15+
}

src/Autofac/Core/Resolving/Middleware/CircularDependencyDetectorMiddleware.cs

Lines changed: 20 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,26 @@ private static bool TryGetActiveDetectionState(
9595
int activationDepth,
9696
out DependencyDetectionState? detectionState)
9797
{
98+
if (operation is IDependencyDetectionStateOwner owner)
99+
{
100+
detectionState = owner.DependencyDetectionState;
101+
102+
if (detectionState is { IsInitialized: true })
103+
{
104+
return true;
105+
}
106+
107+
if (activationDepth >= DetectionCacheActivationDepth)
108+
{
109+
detectionState ??= new DependencyDetectionState();
110+
detectionState.Initialize(requestStack);
111+
owner.DependencyDetectionState = detectionState;
112+
return true;
113+
}
114+
115+
return false;
116+
}
117+
98118
detectionState = null;
99119
var cacheActive = false;
100120

@@ -225,79 +245,4 @@ private void ExecuteCore(ResolveRequestContext context, Action<ResolveRequestCon
225245
}
226246
}
227247
}
228-
229-
private sealed class DependencyDetectionState
230-
{
231-
private static readonly IEqualityComparer<IComponentRegistration> ReferenceComparer = new ComponentRegistrationReferenceComparer();
232-
private readonly Dictionary<IComponentRegistration, int> _registrations = new(ReferenceComparer);
233-
234-
public bool IsInitialized { get; private set; }
235-
236-
public void Initialize(IEnumerable<ResolveRequestContext> existingStack)
237-
{
238-
if (IsInitialized)
239-
{
240-
return;
241-
}
242-
243-
foreach (var context in existingStack)
244-
{
245-
AddRegistration(context.Registration);
246-
}
247-
248-
IsInitialized = true;
249-
}
250-
251-
public bool Contains(IComponentRegistration registration)
252-
{
253-
return IsInitialized && _registrations.ContainsKey(registration);
254-
}
255-
256-
public void Push(IComponentRegistration registration)
257-
{
258-
if (!IsInitialized)
259-
{
260-
return;
261-
}
262-
263-
AddRegistration(registration);
264-
}
265-
266-
public void Pop(IComponentRegistration registration)
267-
{
268-
if (!IsInitialized || !_registrations.TryGetValue(registration, out var count))
269-
{
270-
return;
271-
}
272-
273-
if (count <= 1)
274-
{
275-
_registrations.Remove(registration);
276-
}
277-
else
278-
{
279-
_registrations[registration] = count - 1;
280-
}
281-
}
282-
283-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
284-
private void AddRegistration(IComponentRegistration registration)
285-
{
286-
if (_registrations.TryGetValue(registration, out var count))
287-
{
288-
_registrations[registration] = count + 1;
289-
}
290-
else
291-
{
292-
_registrations.Add(registration, 1);
293-
}
294-
}
295-
296-
private sealed class ComponentRegistrationReferenceComparer : IEqualityComparer<IComponentRegistration>
297-
{
298-
public bool Equals(IComponentRegistration? x, IComponentRegistration? y) => ReferenceEquals(x, y);
299-
300-
public int GetHashCode(IComponentRegistration obj) => RuntimeHelpers.GetHashCode(obj);
301-
}
302-
}
303248
}

src/Autofac/Core/Resolving/ResolveOperation.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Autofac.Core.Resolving;
1212
/// A <see cref="ResolveOperation"/> is a component context that sequences and monitors the multiple
1313
/// activations that go into producing a single requested object graph.
1414
/// </summary>
15-
internal sealed class ResolveOperation : IDependencyTrackingResolveOperation
15+
internal sealed class ResolveOperation : IDependencyTrackingResolveOperation, IDependencyDetectionStateOwner
1616
{
1717
private const int SuccessListInitialCapacity = 32;
1818
private readonly List<DefaultResolveRequestContext> _successfulRequests = new(SuccessListInitialCapacity);
@@ -68,6 +68,9 @@ public ResolveOperation(
6868
/// <inheritdoc/>
6969
public SegmentedStack<ResolveRequestContext> RequestStack { get; } = new SegmentedStack<ResolveRequestContext>();
7070

71+
/// <inheritdoc/>
72+
public DependencyDetectionState? DependencyDetectionState { get; set; }
73+
7174
/// <summary>
7275
/// Gets the <see cref="ResolveRequest" /> that initiated the operation. Other nested requests may have been
7376
/// issued as a result of this one.

0 commit comments

Comments
 (0)