Skip to content

Commit

Permalink
Merge pull request #1148 from alistairjevans/request-stack-size
Browse files Browse the repository at this point in the history
Circular Dependency Changes
  • Loading branch information
tillig committed Jun 10, 2020
2 parents cddba81 + fd8bc4f commit 187c40e
Show file tree
Hide file tree
Showing 13 changed files with 679 additions and 47 deletions.
9 changes: 9 additions & 0 deletions src/Autofac/Autofac.csproj
Expand Up @@ -188,6 +188,11 @@
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="Core\Resolving\SegmentedStackResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>SegmentedStackResources.resx</DependentUpon>
</Compile>
<Compile Update="Core\ServiceResources.Designer.cs">
<DependentUpon>ServiceResources.resx</DependentUpon>
<DesignTime>True</DesignTime>
Expand Down Expand Up @@ -397,6 +402,10 @@
<LastGenOutput>DisposerResources.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Core\Resolving\SegmentedStackResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>SegmentedStackResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Core\ServiceResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ServiceResources.Designer.cs</LastGenOutput>
Expand Down
Expand Up @@ -27,6 +27,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Autofac.Core.Resolving.Pipeline;

namespace Autofac.Core.Resolving.Middleware
Expand Down Expand Up @@ -67,7 +68,16 @@ public void Execute(ResolveRequestContextBase context, Action<ResolveRequestCont

if (activationDepth > _maxResolveDepth)
{
#if NETSTANDARD2_1
// In .NET Standard 2.1 we will try and keep going until we run out of stack space.
if (!RuntimeHelpers.TryEnsureSufficientExecutionStack())
{
throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorMessages.MaxDepthExceeded, context.Service));
}
#else
// Pre .NET Standard 2.1 we just end at 50.
throw new DependencyResolutionException(string.Format(CultureInfo.CurrentCulture, CircularDependencyDetectorMessages.MaxDepthExceeded, context.Service));
#endif
}

var requestStack = context.Operation.RequestStack;
Expand All @@ -78,7 +88,6 @@ public void Execute(ResolveRequestContextBase context, Action<ResolveRequestCont
{
var registration = context.Registration;

// Only check the stack for shared components.
foreach (var requestEntry in requestStack)
{
if (requestEntry.Registration == registration)
Expand Down
6 changes: 4 additions & 2 deletions src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs
Expand Up @@ -267,6 +267,8 @@ private static IResolvePipeline BuildPipeline(MiddlewareDeclaration? lastDecl)

Action<ResolveRequestContextBase> Chain(Action<ResolveRequestContextBase> next, IResolveMiddleware stage)
{
var stagePhase = stage.Phase;

return (ctxt) =>
{
// Optimise the path depending on whether a tracer is attached.
Expand All @@ -276,7 +278,7 @@ Action<ResolveRequestContextBase> Chain(Action<ResolveRequestContextBase> next,
var succeeded = false;
try
{
ctxt.PhaseReached = stage.Phase;
ctxt.PhaseReached = stagePhase;
stage.Execute(ctxt, next);
succeeded = true;
}
Expand All @@ -287,7 +289,7 @@ Action<ResolveRequestContextBase> Chain(Action<ResolveRequestContextBase> next,
}
else
{
ctxt.PhaseReached = stage.Phase;
ctxt.PhaseReached = stagePhase;
stage.Execute(ctxt, next);
}
};
Expand Down
15 changes: 0 additions & 15 deletions src/Autofac/Core/Resolving/Pipeline/ResolveRequestContext.cs
Expand Up @@ -48,21 +48,6 @@ internal sealed class ResolveRequestContext : ResolveRequestContextBase
{
}

/// <inheritdoc/>
public override object ResolveComponent(ResolveRequest request)
{
return Operation.GetOrCreateInstance(ActivationScope, request);
}

/// <inheritdoc/>
public override object ResolveComponentWithNewOperation(ResolveRequest request)
{
// Create a new operation, with the current ActivationScope and Tracer.
// Pass in the current operation as a tracing reference.
var operation = new ResolveOperation(ActivationScope, Tracer, Operation);
return operation.Execute(request);
}

/// <summary>
/// Completes the request context; invokes any event handlers.
/// </summary>
Expand Down
19 changes: 4 additions & 15 deletions src/Autofac/Core/Resolving/Pipeline/ResolveRequestContextBase.cs
Expand Up @@ -162,21 +162,10 @@ public void ChangeParameters(IEnumerable<Parameter> newParameters)
}

/// <inheritdoc />
public abstract object ResolveComponent(ResolveRequest request);

/// <summary>
/// Resolve an instance of the provided registration within the context, but isolated inside a new
/// <see cref="ResolveOperationBase"/>.
/// This method should only be used instead of <see cref="IComponentContext.ResolveComponent(ResolveRequest)"/>
/// if you need to resolve a component with a completely separate operation and circular dependency verification stack.
/// </summary>
/// <param name="request">The resolve request.</param>
/// <returns>
/// The component instance.
/// </returns>
/// <exception cref="ComponentNotRegisteredException"/>
/// <exception cref="DependencyResolutionException"/>
public abstract object ResolveComponentWithNewOperation(ResolveRequest request);
public virtual object ResolveComponent(ResolveRequest request)
{
return Operation.GetOrCreateInstance(ActivationScope, request);
}

/// <summary>
/// Complete the request, raising any appropriate events.
Expand Down
12 changes: 11 additions & 1 deletion src/Autofac/Core/Resolving/ResolveOperationBase.cs
Expand Up @@ -124,7 +124,17 @@ protected ResolveOperationBase(ISharingLifetimeScope mostNestedLifetimeScope, IR
/// Don't want this exposed to the outside world, but we do want it available in the <see cref="CircularDependencyDetectorMiddleware"/>,
/// hence it's internal.
/// </remarks>
internal Stack<ResolveRequestContextBase> RequestStack { get; } = new Stack<ResolveRequestContextBase>();
internal SegmentedStack<ResolveRequestContextBase> RequestStack { get; } = new SegmentedStack<ResolveRequestContextBase>();

/// <summary>
/// Enter a new dependency chain block where subsequent requests inside the operation are allowed to repeat
/// registrations from before the block.
/// </summary>
/// <returns>A disposable that should be disposed to exit the block.</returns>
public IDisposable EnterNewDependencyDetectionBlock()
{
return RequestStack.EnterSegment();
}

/// <inheritdoc />
public event EventHandler<ResolveRequestBeginningEventArgs>? ResolveRequestBeginning;
Expand Down
239 changes: 239 additions & 0 deletions src/Autofac/Core/Resolving/SegmentedStack.cs
@@ -0,0 +1,239 @@
// This software is part of the Autofac IoC container
// Copyright © 2020 Autofac Contributors
// https://autofac.org
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Autofac.Core.Resolving
{
/// <summary>
/// Implements a segmented stack of items, which functions like a regular <see cref="Stack{T}"/>, but allows segments
/// of the stack to be enumerated without including items pushed before the segment.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
internal sealed class SegmentedStack<T> : IEnumerable<T>
where T : class
{
private T[] _array;
private int _next;
private int _activeSegmentBase;

private const int InitialCapacity = 16;

/// <summary>
/// Initializes a new instance of the <see cref="SegmentedStack{T}"/> class.
/// </summary>
public SegmentedStack()
{
_array = new T[InitialCapacity];
}

/// <summary>
/// Push an item onto the stack.
/// </summary>
/// <param name="item">The item.</param>
public void Push(T item)
{
// No null check for item here; internally called method only, known to never be null, and is a very hot path.
var next = _next;
T[] arr = _array;

// Array bounds checking cast.
if ((uint)next < (uint)arr.Length)
{
arr[next] = item;
_next = next + 1;
}
else
{
PushWithResize(item);
}
}

// Do not inline; makes it easier to profile stack resizing.
[MethodImpl(MethodImplOptions.NoInlining)]
private void PushWithResize(T item)
{
Array.Resize(ref _array, 2 * _array.Length);
_array[_next] = item;
_next++;
}

/// <summary>
/// Pop the item at the top of the stack (and return it).
/// </summary>
/// <returns>The item that has just been popped.</returns>
public T Pop()
{
int next = _next - 1;
var array = _array;

// Array bounds checking cast.
if ((uint)next >= (uint)array.Length || next < _activeSegmentBase)
{
// Cannot pop below the active segment position.
throw new InvalidOperationException(SegmentedStackResources.CurrentStackSegmentEmpty);
}

_next = next;
var item = array[next];
array[next] = null!;
return item;
}

/// <summary>
/// Gets the count of the items in the active segment.
/// </summary>
public int Count => _next - _activeSegmentBase;

/// <summary>
/// Enter a new segment. When this method returns <see cref="Count"/> will be zero, and the stack will appear empty.
/// </summary>
/// <returns>An <see cref="IDisposable"/> that will return the stack to the previously active segment when disposed.</returns>
public IDisposable EnterSegment()
{
var reset = new StackSegment(this, _activeSegmentBase);

_activeSegmentBase = _next;

return reset;
}

/// <inheritdoc/>
public IEnumerator<T> GetEnumerator()
{
return new Enumerator(this);
}

/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(this);
}

private struct StackSegment : IDisposable
{
private readonly SegmentedStack<T> _stack;
private readonly int _resetPosition;

public StackSegment(SegmentedStack<T> stack, int resetPosition)
{
_stack = stack;
_resetPosition = resetPosition;
}

public void Dispose()
{
// If the stack 'next' is not just above the active segment base, then
// the segment was not fully popped before exiting the segment.
if (_stack.Count > 0)
{
throw new InvalidOperationException(SegmentedStackResources.CannotExitSegmentWithRemainingItems);
}

_stack._activeSegmentBase = _resetPosition;
}
}

private struct Enumerator : IEnumerator<T>, IEnumerator
{
private readonly SegmentedStack<T> _stack;
private readonly int _activeSegmentBase;
private int _index;
private T? _currentElement;

internal Enumerator(SegmentedStack<T> stack)
{
_stack = stack;
_index = -2;
_activeSegmentBase = _stack._activeSegmentBase;
_currentElement = null;
}

public T Current
{
get
{
if (_index < 0)
{
throw new InvalidOperationException(SegmentedStackResources.EnumeratorNotValid);
}

return _currentElement!;
}
}

object IEnumerator.Current => Current;

public void Dispose()
{
_index = -1;
}

public bool MoveNext()
{
var index = _index;

if (index == -2)
{
// Start the enumerator.
_index = index = _stack._next - 1;

if (index > _activeSegmentBase)
{
_currentElement = _stack._array[index];
return true;
}

return false;
}

if (index == -1)
{
// Finished.
return false;
}

if (--index >= _activeSegmentBase)
{
_currentElement = _stack._array[index];
_index = index;
return true;
}

_currentElement = null!;
return false;
}

public void Reset()
{
_index = -2;
_currentElement = null!;
}
}
}
}

0 comments on commit 187c40e

Please sign in to comment.