Handle IAsyncDisposable scoped objects (#7343)
- We make a scope today around hub invocations, with IAsyncDisposable now implemented in the DI container, we need to support IServiceScope being IAsyncDisposable and IDisposable
This commit is contained in:
parent
cc065f0055
commit
b7d3b3aa13
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
|
{
|
||||||
|
internal static class AsyncDisposableExtensions
|
||||||
|
{
|
||||||
|
// Does a light up check to see if a type is IAsyncDisposable and calls DisposeAsync if it is
|
||||||
|
public static ValueTask DisposeAsync(this IDisposable disposable)
|
||||||
|
{
|
||||||
|
if (disposable is IAsyncDisposable asyncDisposable)
|
||||||
|
{
|
||||||
|
return asyncDisposable.DisposeAsync();
|
||||||
|
}
|
||||||
|
disposable.Dispose();
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,8 +39,12 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
|
|
||||||
public override async Task OnConnectedAsync(HubConnectionContext connection)
|
public override async Task OnConnectedAsync(HubConnectionContext connection)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
IServiceScope scope = null;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
||||||
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
||||||
var hub = hubActivator.Create();
|
var hub = hubActivator.Create();
|
||||||
try
|
try
|
||||||
|
|
@ -53,12 +57,20 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
hubActivator.Release(hub);
|
hubActivator.Release(hub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await scope.DisposeAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task OnDisconnectedAsync(HubConnectionContext connection, Exception exception)
|
public override async Task OnDisconnectedAsync(HubConnectionContext connection, Exception exception)
|
||||||
{
|
{
|
||||||
using (var scope = _serviceScopeFactory.CreateScope())
|
IServiceScope scope = null;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
|
scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|
||||||
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
var hubActivator = scope.ServiceProvider.GetRequiredService<IHubActivator<THub>>();
|
||||||
var hub = hubActivator.Create();
|
var hub = hubActivator.Create();
|
||||||
try
|
try
|
||||||
|
|
@ -71,6 +83,10 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
hubActivator.Release(hub);
|
hubActivator.Release(hub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await scope.DisposeAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task DispatchMessageAsync(HubConnectionContext connection, HubMessage hubMessage)
|
public override Task DispatchMessageAsync(HubConnectionContext connection, HubMessage hubMessage)
|
||||||
|
|
@ -304,7 +320,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
// And normal invocations handle cleanup below in the finally
|
// And normal invocations handle cleanup below in the finally
|
||||||
if (isStreamCall)
|
if (isStreamCall)
|
||||||
{
|
{
|
||||||
CleanupInvocation(connection, hubMethodInvocationMessage, hubActivator, hub, scope);
|
await CleanupInvocation(connection, hubMethodInvocationMessage, hubActivator, hub, scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,17 +358,14 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
{
|
{
|
||||||
if (disposeScope)
|
if (disposeScope)
|
||||||
{
|
{
|
||||||
CleanupInvocation(connection, hubMethodInvocationMessage, hubActivator, hub, scope);
|
await CleanupInvocation(connection, hubMethodInvocationMessage, hubActivator, hub, scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CleanupInvocation(HubConnectionContext connection, HubMethodInvocationMessage hubMessage, IHubActivator<THub> hubActivator,
|
private ValueTask CleanupInvocation(HubConnectionContext connection, HubMethodInvocationMessage hubMessage, IHubActivator<THub> hubActivator,
|
||||||
THub hub, IServiceScope scope)
|
THub hub, IServiceScope scope)
|
||||||
{
|
{
|
||||||
hubActivator?.Release(hub);
|
|
||||||
scope.Dispose();
|
|
||||||
|
|
||||||
if (hubMessage.StreamIds != null)
|
if (hubMessage.StreamIds != null)
|
||||||
{
|
{
|
||||||
foreach (var stream in hubMessage.StreamIds)
|
foreach (var stream in hubMessage.StreamIds)
|
||||||
|
|
@ -365,6 +378,10 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
catch (KeyNotFoundException) { }
|
catch (KeyNotFoundException) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hubActivator?.Release(hub);
|
||||||
|
|
||||||
|
return scope.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StreamResultsAsync(string invocationId, HubConnectionContext connection, IAsyncEnumerator<object> enumerator, IServiceScope scope,
|
private async Task StreamResultsAsync(string invocationId, HubConnectionContext connection, IAsyncEnumerator<object> enumerator, IServiceScope scope,
|
||||||
|
|
@ -398,7 +415,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal
|
||||||
{
|
{
|
||||||
(enumerator as IDisposable)?.Dispose();
|
(enumerator as IDisposable)?.Dispose();
|
||||||
|
|
||||||
CleanupInvocation(connection, hubMethodInvocationMessage, hubActivator, hub, scope);
|
await CleanupInvocation(connection, hubMethodInvocationMessage, hubActivator, hub, scope);
|
||||||
|
|
||||||
// Dispose the linked CTS for the stream.
|
// Dispose the linked CTS for the stream.
|
||||||
streamCts.Dispose();
|
streamCts.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -538,6 +538,21 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class HubWithAsyncDisposable : TestHub
|
||||||
|
{
|
||||||
|
private AsyncDisposable _disposable;
|
||||||
|
|
||||||
|
public HubWithAsyncDisposable(AsyncDisposable disposable)
|
||||||
|
{
|
||||||
|
_disposable = disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Test()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class AbortHub : Hub
|
public class AbortHub : Hub
|
||||||
{
|
{
|
||||||
public void Kill()
|
public void Kill()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -91,4 +92,20 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
{
|
{
|
||||||
public int DisposeCount = 0;
|
public int DisposeCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AsyncDisposable : IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly TrackDispose _trackDispose;
|
||||||
|
|
||||||
|
public AsyncDisposable(TrackDispose trackDispose)
|
||||||
|
{
|
||||||
|
_trackDispose = trackDispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
_trackDispose.DisposeCount++;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,37 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsyncDisposablesInHubsAreSupported()
|
||||||
|
{
|
||||||
|
using (StartVerifiableLog())
|
||||||
|
{
|
||||||
|
var trackDispose = new TrackDispose();
|
||||||
|
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider(s =>
|
||||||
|
{
|
||||||
|
s.AddScoped<AsyncDisposable>();
|
||||||
|
s.AddSingleton(trackDispose);
|
||||||
|
},
|
||||||
|
LoggerFactory);
|
||||||
|
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<HubWithAsyncDisposable>>();
|
||||||
|
|
||||||
|
using (var client = new TestClient())
|
||||||
|
{
|
||||||
|
var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
|
||||||
|
|
||||||
|
var result = (await client.InvokeAsync(nameof(HubWithAsyncDisposable.Test)).OrTimeout());
|
||||||
|
Assert.NotNull(result);
|
||||||
|
|
||||||
|
// kill the connection
|
||||||
|
client.Dispose();
|
||||||
|
|
||||||
|
await connectionHandlerTask;
|
||||||
|
|
||||||
|
Assert.Equal(3, trackDispose.DisposeCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ConnectionAbortedTokenTriggers()
|
public async Task ConnectionAbortedTokenTriggers()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue