Skip to content
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

Adding IAsyncDisposable Support to Autofac #1037

Merged
merged 8 commits into from Oct 20, 2019
13 changes: 11 additions & 2 deletions src/Autofac/Autofac.csproj
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Autofac is an IoC container for Microsoft .NET. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity.</Description>
<VersionPrefix>5.0.0</VersionPrefix>
<TargetFrameworks>netstandard2.0;netstandard1.1;net45</TargetFrameworks>
<TargetFrameworks>netcoreapp3.0;netstandard2.0;netstandard1.1;net45</TargetFrameworks>
alistairjevans marked this conversation as resolved.
Show resolved Hide resolved
<LangVersion>latest</LangVersion>
<NoWarn>$(NoWarn);CS1591;IDE0008</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Expand Down Expand Up @@ -150,10 +150,15 @@
<AutoGen>True</AutoGen>
<DependentUpon>ResolveOperationResources.resx</DependentUpon>
</Compile>
<Compile Update="Core\ServiceResources.Designer.cs">
<Compile Update="Core\DisposerResources.Designer.cs">
<DependentUpon>DisposerResources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="Core\ServiceResources.Designer.cs">
<DependentUpon>ServiceResources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="Features\Collections\CollectionRegistrationSourceResources.Designer.cs">
<DesignTime>True</DesignTime>
Expand Down Expand Up @@ -334,6 +339,10 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ResolveOperationResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Core\DisposerResources.resx">
<LastGenOutput>DisposerResources.Designer.cs</LastGenOutput>
<Generator>ResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Core\ServiceResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ServiceResources.Designer.cs</LastGenOutput>
Expand Down
17 changes: 17 additions & 0 deletions src/Autofac/Core/Container.cs
Expand Up @@ -27,6 +27,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Autofac.Core.Activators.Delegate;
using Autofac.Core.Lifetime;
using Autofac.Core.Registration;
Expand Down Expand Up @@ -178,6 +179,22 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}

#if NETCOREAPP3_0
protected override async ValueTask DisposeAsync(bool disposing)
{
if (disposing)
{
await _rootLifetimeScope.DisposeAsync();

// Registries are not likely to have async tasks to dispose of,
// so we will leave it as a straight dispose.
ComponentRegistry.Dispose();
}

// Do not call the base, otherwise the standard Dispose will fire.
}
#endif

/// <summary>
/// Gets the service object of the specified type.
/// </summary>
Expand Down
112 changes: 106 additions & 6 deletions src/Autofac/Core/Disposer.cs
Expand Up @@ -25,6 +25,8 @@

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Autofac.Util;

namespace Autofac.Core
Expand All @@ -36,11 +38,13 @@ namespace Autofac.Core
internal class Disposer : Disposable, IDisposer
{
/// <summary>
/// Contents all implement IDisposable.
/// Contents all implement IDisposable or IAsyncDisposable.
/// </summary>
private Stack<IDisposable> _items = new Stack<IDisposable>();
private Stack<object> _items = new Stack<object>();

private readonly object _synchRoot = new object();
// Need to use a semaphore instead of a simple object to lock on, because
// we need to synchronoise an awaitable block.
private SemaphoreSlim _synchRoot = new SemaphoreSlim(1, 1);

/// <summary>
/// Releases unmanaged and - optionally - managed resources.
Expand All @@ -50,32 +54,128 @@ protected override void Dispose(bool disposing)
{
if (disposing)
{
lock (_synchRoot)
_synchRoot.Wait();
try
{
while (_items.Count > 0)
{
var item = _items.Pop();
item.Dispose();

// If we are in synchronous dispose, and an object doesn't implement
if (item is IDisposable disposable)
{
disposable.Dispose();
}
else
{
// Type only implements IAsyncDisposable, which is not valid if there is a synchronous dispose being done.
throw new InvalidOperationException(string.Format(
DisposerResources.Culture,
DisposerResources.TypeOnlyImplementsIAsyncDisposable,
item.GetType().FullName));
}
}

_items = null;
}
finally
{
_synchRoot.Release();

// We don't need the semaphore any more.
_synchRoot.Dispose();
}
}

base.Dispose(disposing);
}

#if NETCOREAPP3_0
protected override async ValueTask DisposeAsync(bool disposing)
{
if (disposing)
{
// Acquire our semaphore.
await _synchRoot.WaitAsync().ConfigureAwait(false);
try
{
while (_items.Count > 0)
{
var item = _items.Pop();

// If the item implements IAsyncDisposable we will call its DisposeAsync Method.
if (item is IAsyncDisposable asyncDisposable)
{
var vt = asyncDisposable.DisposeAsync();

// Don't await if it's already completed (this is a slight gain in performance of using ValueTask).
if (!vt.IsCompletedSuccessfully)
{
await vt.ConfigureAwait(false);
}
}
else if (item is IDisposable disposable)
{
// Call the standard Dispose.
disposable.Dispose();
}
}

_items = null;
}
finally
{
_synchRoot.Release();

// We don't need the semaphore any more.
_synchRoot.Dispose();
}
}
}

/// <summary>
/// Adds an object to the disposer, where that object only implements IAsyncDisposable. When the disposer is
/// disposed, so will the object be.
/// This is not typically recommended, and you should implement IDisposable as well.
/// </summary>
/// <param name="instance">The instance.</param>
/// <remarks>
/// If this Disposer is disposed of using a synchronous Dispose call, that call will throw an exception.
/// </remarks>
public void AddInstanceForAsyncDisposal(IAsyncDisposable instance)
{
AddInternal(instance);
}
#endif

/// <summary>
/// Adds an object to the disposer. When the disposer is
/// disposed, so will the object be.
/// </summary>
/// <param name="instance">The instance.</param>
public void AddInstanceForDisposal(IDisposable instance)
{
AddInternal(instance);
}

private void AddInternal(object instance)
{
if (instance == null) throw new ArgumentNullException(nameof(instance));

lock (_synchRoot)
if (IsDisposed)
{
throw new ObjectDisposedException(nameof(Disposer), DisposerResources.CannotAddToDisposedDisposer);
}

_synchRoot.Wait();
try
{
_items.Push(instance);
}
finally
{
_synchRoot.Release();
}
}
}
}
82 changes: 82 additions & 0 deletions src/Autofac/Core/DisposerResources.Designer.cs

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