Ensure CircuitHost disposes DI scope even if other disposals throw

This commit is contained in:
Steve Sanderson 2019-07-04 10:45:35 +01:00
parent 002cb51d72
commit ee1cbda155
2 changed files with 57 additions and 4 deletions

View File

@ -293,10 +293,16 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
await Renderer.InvokeAsync(async () =>
{
await OnConnectionDownAsync(CancellationToken.None);
await OnCircuitDownAsync();
Renderer.Dispose();
_scope.Dispose();
try
{
await OnConnectionDownAsync(CancellationToken.None);
await OnCircuitDownAsync();
}
finally
{
Renderer.Dispose();
_scope.Dispose();
}
});
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
@ -40,6 +41,37 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
Assert.True(remoteRenderer.Disposed);
}
[Fact]
public async Task DisposeAsync_DisposesResourcesEvenIfCircuitHandlerOrComponentThrows()
{
// Arrange
var serviceScope = new Mock<IServiceScope>();
var handler = new Mock<CircuitHandler>();
handler
.Setup(h => h.OnCircuitClosedAsync(It.IsAny<Circuit>(), It.IsAny<CancellationToken>()))
.Throws<InvalidTimeZoneException>();
var remoteRenderer = GetRemoteRenderer(Renderer.CreateDefaultDispatcher());
var circuitHost = TestCircuitHost.Create(
Guid.NewGuid().ToString(),
serviceScope.Object,
remoteRenderer,
handlers: new[] { handler.Object });
var throwOnDisposeComponent = new ThrowOnDisposeComponent();
circuitHost.Renderer.AssignRootComponentId(throwOnDisposeComponent);
// Act
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () =>
{
await circuitHost.DisposeAsync();
});
// Assert
Assert.True(throwOnDisposeComponent.DidCallDispose);
serviceScope.Verify(scope => scope.Dispose(), Times.Once());
Assert.True(remoteRenderer.Disposed);
}
[Fact]
public async Task DisposeAsync_DisposesRendererWithinSynchronizationContext()
{
@ -239,5 +271,20 @@ namespace Microsoft.AspNetCore.Components.Server.Circuits
Assert.Same(Dispatcher, SynchronizationContext.Current);
}
}
private class ThrowOnDisposeComponent : IComponent, IDisposable
{
public bool DidCallDispose { get; private set; }
public void Configure(RenderHandle renderHandle) { }
public Task SetParametersAsync(ParameterCollection parameters)
=> Task.CompletedTask;
public void Dispose()
{
DidCallDispose = true;
throw new InvalidFilterCriteriaException();
}
}
}
}