From ee1cbda15533dc9c28aed4d2800dc6eedb5e0c10 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Thu, 4 Jul 2019 10:45:35 +0100 Subject: [PATCH] Ensure CircuitHost disposes DI scope even if other disposals throw --- .../Server/src/Circuits/CircuitHost.cs | 14 ++++-- .../Server/test/Circuits/CircuitHostTest.cs | 47 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/Components/Server/src/Circuits/CircuitHost.cs b/src/Components/Server/src/Circuits/CircuitHost.cs index 4e3daa9dd7..f57e7ce141 100644 --- a/src/Components/Server/src/Circuits/CircuitHost.cs +++ b/src/Components/Server/src/Circuits/CircuitHost.cs @@ -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(); + } }); } diff --git a/src/Components/Server/test/Circuits/CircuitHostTest.cs b/src/Components/Server/test/Circuits/CircuitHostTest.cs index 78d413c298..65605e17eb 100644 --- a/src/Components/Server/test/Circuits/CircuitHostTest.cs +++ b/src/Components/Server/test/Circuits/CircuitHostTest.cs @@ -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(); + var handler = new Mock(); + handler + .Setup(h => h.OnCircuitClosedAsync(It.IsAny(), It.IsAny())) + .Throws(); + 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(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(); + } + } } }