Skip to content

Use ephemeral service info blocks for any lookups before the registry is built. #1220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Autofac/Core/Registration/ComponentRegistryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ protected override void Dispose(bool disposing)
/// <returns>A new component registry with the configured component registrations.</returns>
public IComponentRegistry Build()
{
// Mark our tracker as complete; no more adjustments to the registry will be made after this point.
_registeredServicesTracker.Complete();

// Go through all our registrations and build the component pipeline for each one.
foreach (var registration in _registeredServicesTracker.Registrations)
{
Expand Down
82 changes: 75 additions & 7 deletions src/Autofac/Core/Registration/DefaultRegisteredServicesTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ internal class DefaultRegisteredServicesTracker : Disposable, IRegisteredService

private readonly List<IServiceMiddlewareSource> _servicePipelineSources = new List<IServiceMiddlewareSource>();

private Dictionary<Service, ServiceRegistrationInfo>? _ephemeralServiceInfo;
private bool _trackerPopulationComplete;

/// <summary>
/// Initializes a new instance of the <see cref="DefaultRegisteredServicesTracker"/> class.
/// </summary>
Expand Down Expand Up @@ -93,16 +96,30 @@ public virtual void AddRegistration(IComponentRegistration registration, bool pr
foreach (var service in registration.Services)
{
var info = GetServiceInfo(service);

// We are in an ephemeral initialization; use the ephemeral set.
if (_ephemeralServiceInfo is object)
{
info = GetEphemeralServiceInfo(_ephemeralServiceInfo, service, info);
}

info.AddImplementation(registration, preserveDefaults, originatedFromDynamicSource);
}

_registrations.Add(registration);
var handler = Registered;
handler?.Invoke(this, registration);

if (originatedFromDynamicSource)
if (_ephemeralServiceInfo is null)
{
registration.BuildResolvePipeline(this);
// Only when we are keeping the populated service information will we store registrations and
// build pipelines for them.
// The Registrations collection is only available to consumers once the tracker is contained with a ContainerRegistry
// and the Complete method has been called.
_registrations.Add(registration);
var handler = Registered;
handler?.Invoke(this, registration);

if (originatedFromDynamicSource)
{
registration.BuildResolvePipeline(this);
}
}
}

Expand Down Expand Up @@ -213,6 +230,12 @@ public IEnumerable<ServiceRegistration> ServiceRegistrationsFor(Service service)
return list;
}

/// <inheritdoc/>
public void Complete()
{
_trackerPopulationComplete = true;
}

/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
Expand All @@ -229,12 +252,26 @@ protected override void Dispose(bool disposing)

private ServiceRegistrationInfo GetInitializedServiceInfo(Service service)
{
var createdEphemeralSet = false;

var info = GetServiceInfo(service);
if (info.IsInitialized)
{
return info;
}

if (!_trackerPopulationComplete)
{
// We need an ephemeral set for this pre-complete intialization.
if (_ephemeralServiceInfo is null)
{
_ephemeralServiceInfo = new Dictionary<Service, ServiceRegistrationInfo>();
createdEphemeralSet = true;
}

info = GetEphemeralServiceInfo(_ephemeralServiceInfo, service, info);
}

var succeeded = false;
var lockTaken = false;
try
Expand Down Expand Up @@ -268,6 +305,12 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service)
continue;
}

if (_ephemeralServiceInfo is object)
{
// Use ephemeral info for additional services.
additionalInfo = GetEphemeralServiceInfo(_ephemeralServiceInfo, service, info);
}

if (!additionalInfo.IsInitializing)
{
BeginServiceInfoInitialization(additionalService, additionalInfo, ExcludeSource(_dynamicRegistrationSources, next));
Expand All @@ -278,7 +321,10 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service)
}
}

AddRegistration(provided, true, true);
AddRegistration(
provided,
preserveDefaults: true,
originatedFromDynamicSource: true);
}
}

Expand All @@ -297,6 +343,14 @@ private ServiceRegistrationInfo GetInitializedServiceInfo(Service service)
{
Monitor.Exit(info);
}

// This method was the entry point to an ephemeral service info initialization.
// We need to discard it, so the next set of ephemeral service info is done from scratch.
if (createdEphemeralSet)
{
_ephemeralServiceInfo?.Clear();
_ephemeralServiceInfo = null;
}
}

return info;
Expand Down Expand Up @@ -330,5 +384,19 @@ private ServiceRegistrationInfo GetServiceInfo(Service service)
{
return _serviceInfo.GetOrAdd(service, RegInfoFactory);
}

private static ServiceRegistrationInfo GetEphemeralServiceInfo(Dictionary<Service, ServiceRegistrationInfo> ephemeralSet, Service service, ServiceRegistrationInfo info)
{
if (ephemeralSet.TryGetValue(service, out var ephemeral))
{
return ephemeral;
}

var newCopy = info.CloneUninitialized();

ephemeralSet.Add(service, newCopy);

return newCopy;
}
}
}
6 changes: 6 additions & 0 deletions src/Autofac/Core/Registration/IRegisteredServicesTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ internal interface IRegisteredServicesTracker : IDisposable, IComponentRegistryS
/// </summary>
event EventHandler<IRegistrationSource> RegistrationSourceAdded;

/// <summary>
/// Should be called prior to the construction of a <see cref="ComponentRegistry" /> to
/// indicate that the tracker is complete, and requested service information should no longer be ephemeral.
/// </summary>
void Complete();

/// <summary>
/// Gets the registered components.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,7 @@ internal ScopeRestrictedRegisteredServicesTracker(IComponentLifetime restrictedR
_restrictedRootScopeLifetime = restrictedRootScopeLifetime;
}

/// <summary>
/// Adds a registration to the list of registered services.
/// </summary>
/// <param name="registration">The registration to add.</param>
/// <param name="preserveDefaults">Indicates whehter the defaults should be preserved.</param>
/// <param name="originatedFromSource">Indicates whether this is an explicitly added registration or that it has been added by a different source.</param>
/// <inheritdoc/>
public override void AddRegistration(IComponentRegistration registration, bool preserveDefaults, bool originatedFromSource = false)
{
if (registration == null)
Expand Down
44 changes: 38 additions & 6 deletions src/Autofac/Core/Registration/ServiceRegistrationInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ public bool IsInitialized
private set => _isInitialized = value;
}

/// <summary>
/// Gets the target registration of a service redirection applied by a particular piece of middleware.
/// </summary>
public IComponentRegistration? RedirectionTargetRegistration { get; private set; }

/// <summary>
/// Gets or sets a value representing the current initialization depth. Will always be zero for initialized service blocks.
/// </summary>
Expand Down Expand Up @@ -166,7 +161,6 @@ public bool IsRegistered
}

private bool Any =>
RedirectionTargetRegistration is object ||
_defaultImplementations.Count > 0 ||
_sourceImplementations != null ||
_preserveDefaultImplementations != null;
Expand Down Expand Up @@ -366,6 +360,44 @@ private IResolvePipeline BuildPipeline()
}
}

/// <summary>
/// Creates a copy of an uninitialized <see cref="ServiceRegistrationInfo"/>, preserving existing registrations and custom middleware.
/// </summary>
/// <returns>A new service registration info block.</returns>
/// <exception cref="InvalidOperationException">Thrown if the service registration has been initialized already.</exception>
public ServiceRegistrationInfo CloneUninitialized()
{
if (InitializationDepth != 0 || IsInitializing || IsInitialized)
{
throw new InvalidOperationException(ServiceRegistrationInfoResources.NotAfterInitialization);
}

var copy = new ServiceRegistrationInfo(_service)
{
_fixedRegistration = _fixedRegistration,
_defaultImplementation = _defaultImplementation,
};

if (_sourceImplementations is object)
{
copy._sourceImplementations = new List<IComponentRegistration>(_sourceImplementations);
}

if (_preserveDefaultImplementations is object)
{
copy._preserveDefaultImplementations = new List<IComponentRegistration>(_preserveDefaultImplementations);
}

copy._defaultImplementations.AddRange(_defaultImplementations);

if (_customPipelineBuilder is object)
{
copy._customPipelineBuilder = _customPipelineBuilder.Clone();
}

return copy;
}

/// <inheritdoc/>
IEnumerable<IResolveMiddleware> IResolvePipelineBuilder.Middleware => ServiceMiddleware;

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="NotAfterInitialization" xml:space="preserve">
<value>The operation is only valid before the object is initialized.</value>
</data>
<data name="NotDuringInitialization" xml:space="preserve">
<value>The operation is only valid during initialization.</value>
</data>
Expand Down
109 changes: 109 additions & 0 deletions test/Autofac.Specification.Test/Features/DecoratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,82 @@ public void StartableTypesCanBeDecorated()
Assert.True(implementation.Started);
}

[Fact]
public void OpenGenericCanBeDecoratedFromInsideAModuleDecoratorRegisteredFirst()
{
var activatedInstances = new List<object>();

var builder = new ContainerBuilder();

builder.RegisterModule(new MyModule(b => b.RegisterGenericDecorator(typeof(GenericDecorator<>), typeof(IGenericService<>))));

builder.RegisterGeneric(typeof(GenericComponent<>)).As(typeof(IGenericService<>));

var container = builder.Build();

var instance = container.Resolve<IGenericService<int>>();

Assert.IsType<GenericDecorator<int>>(instance);
Assert.IsType<GenericComponent<int>>(((GenericDecorator<int>)instance).Decorated);
}

[Fact]
public void OpenGenericCanBeDecoratedFromInsideAModuleDecoratorRegisteredSecond()
{
var activatedInstances = new List<object>();

var builder = new ContainerBuilder();

builder.RegisterGeneric(typeof(GenericComponent<>)).As(typeof(IGenericService<>));

builder.RegisterModule(new MyModule(b => b.RegisterGenericDecorator(typeof(GenericDecorator<>), typeof(IGenericService<>))));

var container = builder.Build();

var instance = container.Resolve<IGenericService<int>>();

Assert.IsType<GenericDecorator<int>>(instance);
Assert.IsType<GenericComponent<int>>(((GenericDecorator<int>)instance).Decorated);
}

[Fact]
public void OpenGenericInModuleCanBeDecoratoredByDecoratorOutsideModuleWhereModuleRegisteredFirst()
{
var activatedInstances = new List<object>();

var builder = new ContainerBuilder();

builder.RegisterModule(new MyModule(b => b.RegisterGeneric(typeof(GenericComponent<>)).As(typeof(IGenericService<>))));

builder.RegisterGenericDecorator(typeof(GenericDecorator<>), typeof(IGenericService<>));

var container = builder.Build();

var instance = container.Resolve<IGenericService<int>>();

Assert.IsType<GenericDecorator<int>>(instance);
Assert.IsType<GenericComponent<int>>(((GenericDecorator<int>)instance).Decorated);
}

[Fact]
public void OpenGenericInModuleCanBeDecoratoredByDecoratorOutsideModuleWhereModuleRegisteredSecond()
{
var activatedInstances = new List<object>();

var builder = new ContainerBuilder();

builder.RegisterGenericDecorator(typeof(GenericDecorator<>), typeof(IGenericService<>));

builder.RegisterModule(new MyModule(b => b.RegisterGeneric(typeof(GenericComponent<>)).As(typeof(IGenericService<>))));

var container = builder.Build();

var instance = container.Resolve<IGenericService<int>>();

Assert.IsType<GenericDecorator<int>>(instance);
Assert.IsType<GenericComponent<int>>(((GenericDecorator<int>)instance).Decorated);
}

private class MyMetadata
{
public int A { get; set; }
Expand Down Expand Up @@ -1380,5 +1456,38 @@ public void Start()
Decorated.Start();
}
}

private class MyModule : Module
{
private readonly Action<ContainerBuilder> _callback;

public MyModule(Action<ContainerBuilder> callback)
{
_callback = callback;
}

protected override void Load(ContainerBuilder builder)
{
_callback(builder);
}
}

private interface IGenericService<T>
{
}

private class GenericComponent<T> : IGenericService<T>
{
}

private class GenericDecorator<T> : IGenericService<T>
{
public GenericDecorator(IGenericService<T> decorated)
{
Decorated = decorated;
}

public IGenericService<T> Decorated { get; }
}
}
}
Loading