From 3e69fdc4ade8071ff8b117bf1f8a77286e2e1dfa Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 13 Apr 2018 09:16:23 -0700 Subject: [PATCH] Delegate disposable to the IConnectionFactory (#1999) - Added DisposeAsync to the IConnectionFactory. It's responsible for disposing the connection after the pipe has closed. - Added dispose callback to WithConnectionFactory - Don't wait for poll request to end before unwinding from the transport - Make sure all http requests are done before returning from StopAsync in both SSE and longpolling --- .../HubConnectionSendBenchmark.cs | 6 +++ .../HubConnectionStartBenchmark.cs | 6 +++ .../Tcp/TcpHubConnectionBuilderExtensions.cs | 5 +- .../Internal/LongPollingTransport.cs | 23 ++++----- .../Internal/ServerSentEventsTransport.cs | 4 ++ .../HubConnection.cs | 9 ++-- .../HubConnectionBuilderExtensions.cs | 15 ++++-- .../IConnectionFactory.cs | 2 + .../HttpConnectionFactory.cs | 5 ++ .../HubConnectionTests.cs | 15 ++++-- .../HubConnectionBuilderTests.cs | 4 +- .../HubConnectionTests.ConnectionLifecycle.cs | 48 +++++++++++++++++-- .../HubConnectionTests.Helpers.cs | 4 +- .../HubConnectionTests.cs | 3 +- .../LongPollingTransportTests.cs | 14 +++++- .../TestConnection.cs | 2 - .../EndToEndTests.cs | 11 +++-- 17 files changed, 135 insertions(+), 41 deletions(-) diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionSendBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionSendBenchmark.cs index 6093169de7..ba5ebbe52c 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionSendBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionSendBenchmark.cs @@ -56,6 +56,12 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks connection.Features.Set(new TestConnectionInherentKeepAliveFeature()); connection.Transport = _pipe; return Task.FromResult(connection); + }, + connection => + { + connection.Transport.Output.Complete(); + connection.Transport.Input.Complete(); + return Task.CompletedTask; }); _hubConnection = hubConnectionBuilder.Build(); diff --git a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionStartBenchmark.cs b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionStartBenchmark.cs index e3b97cd876..1c4b0b31ca 100644 --- a/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionStartBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.SignalR.Microbenchmarks/HubConnectionStartBenchmark.cs @@ -46,6 +46,12 @@ namespace Microsoft.AspNetCore.SignalR.Microbenchmarks connection.Features.Set(new TestConnectionInherentKeepAliveFeature()); connection.Transport = _pipe; return Task.FromResult(connection); + }, + connection => + { + connection.Transport.Output.Complete(); + connection.Transport.Input.Complete(); + return Task.CompletedTask; }); _hubConnection = hubConnectionBuilder.Build(); diff --git a/samples/ClientSample/Tcp/TcpHubConnectionBuilderExtensions.cs b/samples/ClientSample/Tcp/TcpHubConnectionBuilderExtensions.cs index 50cf2709eb..200ffad9b8 100644 --- a/samples/ClientSample/Tcp/TcpHubConnectionBuilderExtensions.cs +++ b/samples/ClientSample/Tcp/TcpHubConnectionBuilderExtensions.cs @@ -31,7 +31,10 @@ namespace Microsoft.AspNetCore.SignalR.Client public static IHubConnectionBuilder WithEndPoint(this IHubConnectionBuilder builder, EndPoint endPoint) { - builder.WithConnectionFactory(format => new TcpConnection(endPoint).StartAsync()); + builder.WithConnectionFactory( + format => new TcpConnection(endPoint).StartAsync(), + connection => ((TcpConnection)connection).DisposeAsync() + ); return builder; } diff --git a/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/LongPollingTransport.cs b/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/LongPollingTransport.cs index 3678910d04..aa8214a3de 100644 --- a/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/LongPollingTransport.cs +++ b/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/LongPollingTransport.cs @@ -15,8 +15,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { public partial class LongPollingTransport : ITransport { - private static readonly TimeSpan DefaultShutdownTimeout = TimeSpan.FromSeconds(5); - private readonly HttpClient _httpClient; private readonly ILogger _logger; private IDuplexPipe _application; @@ -32,8 +30,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal public PipeWriter Output => _transport.Output; - internal TimeSpan ShutdownTimeout { get; set; } - public LongPollingTransport(HttpClient httpClient) : this(httpClient, null) { } @@ -42,7 +38,6 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal { _httpClient = httpClient; _logger = (loggerFactory ?? NullLoggerFactory.Instance).CreateLogger(); - ShutdownTimeout = DefaultShutdownTimeout; } public Task StartAsync(Uri url, TransferFormat transferFormat) @@ -85,6 +80,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal // Cancel the application so that ReadAsync yields _application.Input.CancelPendingRead(); + + await sending; } else { @@ -95,12 +92,12 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal // This will also cause the poll to return. await SendDeleteRequest(url); - // This timeout is only to ensure the poll is cleaned up despite a misbehaving server. - // It doesn't need to be configurable. - _transportCts.CancelAfter(ShutdownTimeout); + _transportCts.Cancel(); // Cancel any pending flush so that we can quit _application.Output.CancelPendingFlush(); + + await receiving; } } @@ -199,18 +196,18 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal } } - private async Task SendDeleteRequest(Uri pollUrl) + private async Task SendDeleteRequest(Uri url) { try { - Log.SendingDeleteRequest(_logger, pollUrl); - var response = await _httpClient.DeleteAsync(pollUrl); + Log.SendingDeleteRequest(_logger, url); + var response = await _httpClient.DeleteAsync(url); response.EnsureSuccessStatusCode(); - Log.DeleteRequestAccepted(_logger, pollUrl); + Log.DeleteRequestAccepted(_logger, url); } catch (Exception ex) { - Log.ErrorSendingDeleteRequest(_logger, pollUrl, ex); + Log.ErrorSendingDeleteRequest(_logger, url, ex); } } } diff --git a/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/ServerSentEventsTransport.cs b/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/ServerSentEventsTransport.cs index ec126556de..88ee3f97e1 100644 --- a/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/ServerSentEventsTransport.cs +++ b/src/Microsoft.AspNetCore.Http.Connections.Client/Internal/ServerSentEventsTransport.cs @@ -101,6 +101,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal // Cancel the application so that ReadAsync yields _application.Input.CancelPendingRead(); + + await sending; } else { @@ -111,6 +113,8 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal // Cancel any pending flush so that we can quit _application.Output.CancelPendingFlush(); + + await receiving; } } diff --git a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.cs b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.cs index 6302237ca7..115bef8ad6 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnection.cs @@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.SignalR.Client Log.ErrorStartingConnection(_logger, ex); // Can't have any invocations to cancel, we're in the lock. - Complete(startingConnectionState.Connection); + await CloseAsync(startingConnectionState.Connection); throw; } @@ -160,10 +160,9 @@ namespace Microsoft.AspNetCore.SignalR.Client } } - private static void Complete(ConnectionContext connection) + private Task CloseAsync(ConnectionContext connection) { - connection.Transport.Output.Complete(); - connection.Transport.Input.Complete(); + return _connectionFactory.DisposeAsync(connection); } // This method does both Dispose and Start, the 'disposing' flag indicates which. @@ -661,7 +660,7 @@ namespace Microsoft.AspNetCore.SignalR.Client timeoutTimer?.Dispose(); // Dispose the connection - Complete(connectionState.Connection); + await CloseAsync(connectionState.Connection); // Cancel any outstanding invocations within the connection lock connectionState.CancelOutstandingInvocations(connectionState.CloseException); diff --git a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionBuilderExtensions.cs b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionBuilderExtensions.cs index 2fcb50273a..b8749fdb44 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client.Core/HubConnectionBuilderExtensions.cs @@ -13,13 +13,15 @@ namespace Microsoft.AspNetCore.SignalR.Client { public static class HubConnectionBuilderExtensions { - public static IHubConnectionBuilder WithConnectionFactory(this IHubConnectionBuilder hubConnectionBuilder, Func> connectionFactory) + public static IHubConnectionBuilder WithConnectionFactory(this IHubConnectionBuilder hubConnectionBuilder, + Func> connectionFactory, + Func disposeCallback) { if (connectionFactory == null) { throw new ArgumentNullException(nameof(connectionFactory)); } - hubConnectionBuilder.Services.AddSingleton(new DelegateConnectionFactory(connectionFactory)); + hubConnectionBuilder.Services.AddSingleton(new DelegateConnectionFactory(connectionFactory, disposeCallback)); return hubConnectionBuilder; } @@ -38,16 +40,23 @@ namespace Microsoft.AspNetCore.SignalR.Client private class DelegateConnectionFactory : IConnectionFactory { private readonly Func> _connectionFactory; + private readonly Func _disposeCallback; - public DelegateConnectionFactory(Func> connectionFactory) + public DelegateConnectionFactory(Func> connectionFactory, Func disposeCallback) { _connectionFactory = connectionFactory; + _disposeCallback = disposeCallback; } public Task ConnectAsync(TransferFormat transferFormat) { return _connectionFactory(transferFormat); } + + public Task DisposeAsync(ConnectionContext connection) + { + return _disposeCallback(connection); + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client.Core/IConnectionFactory.cs b/src/Microsoft.AspNetCore.SignalR.Client.Core/IConnectionFactory.cs index 77b818f29e..13f24a7d18 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client.Core/IConnectionFactory.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client.Core/IConnectionFactory.cs @@ -9,5 +9,7 @@ namespace Microsoft.AspNetCore.SignalR.Client public interface IConnectionFactory { Task ConnectAsync(TransferFormat transferFormat); + + Task DisposeAsync(ConnectionContext connection); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.SignalR.Client/HttpConnectionFactory.cs b/src/Microsoft.AspNetCore.SignalR.Client/HttpConnectionFactory.cs index 519e82f710..d78cedcd93 100644 --- a/src/Microsoft.AspNetCore.SignalR.Client/HttpConnectionFactory.cs +++ b/src/Microsoft.AspNetCore.SignalR.Client/HttpConnectionFactory.cs @@ -37,5 +37,10 @@ namespace Microsoft.AspNetCore.SignalR.Client await connection.StartAsync(transferFormat); return connection; } + + public Task DisposeAsync(ConnectionContext connection) + { + return ((HttpConnection)connection).DisposeAsync(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs index e6aaa51a64..139bf514ee 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.FunctionalTests/HubConnectionTests.cs @@ -51,7 +51,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests var hubConnectionBuilder = new HubConnectionBuilder(); hubConnectionBuilder.WithHubProtocol(protocol); hubConnectionBuilder.WithLoggerFactory(loggerFactory); - hubConnectionBuilder.WithConnectionFactory(GetHttpConnectionFactory(loggerFactory, path, transportType ?? HttpTransportType.LongPolling | HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents)); + hubConnectionBuilder.WithConnectionFactory(GetHttpConnectionFactory(loggerFactory, path, transportType ?? HttpTransportType.LongPolling | HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents), + connection => ((HttpConnection)connection).DisposeAsync()); return hubConnectionBuilder.Build(); } @@ -896,8 +897,16 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests var stopTask = hubConnection.StopAsync(); - // Stop async and wait for the poll to shut down. It should do so very quickly because the DELETE will stop the poll! - await pollTracker.ActivePoll.OrTimeout(TimeSpan.FromMilliseconds(100)); + try + { + // if we completed running before the poll or after the poll started then the task + // might complete successfully + await pollTracker.ActivePoll.OrTimeout(); + } + catch (OperationCanceledException) + { + // If this happens it's fine because we were in the middle of a poll + } await stopTask; } diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionBuilderTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionBuilderTests.cs index 2c37542e65..0e3db64ec1 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionBuilderTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionBuilderTests.cs @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests return Task.FromResult(null); }; - var serviceProvider = new HubConnectionBuilder().WithConnectionFactory(connectionFactory).Services.BuildServiceProvider(); + var serviceProvider = new HubConnectionBuilder().WithConnectionFactory(connectionFactory, connection => Task.CompletedTask).Services.BuildServiceProvider(); var factory = serviceProvider.GetService(); Assert.NotNull(factory); @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests [Fact] public void BuildCanOnlyBeCalledOnce() { - var builder = new HubConnectionBuilder().WithConnectionFactory(format => null); + var builder = new HubConnectionBuilder().WithConnectionFactory(format => null, connection => Task.CompletedTask); Assert.NotNull(builder.Build()); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.ConnectionLifecycle.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.ConnectionLifecycle.cs index e82445494e..0295b566d6 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.ConnectionLifecycle.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.ConnectionLifecycle.cs @@ -27,14 +27,14 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests private HubConnection CreateHubConnection(TestConnection testConnection) { var builder = new HubConnectionBuilder(); - builder.WithConnectionFactory(format => testConnection.StartAsync(format)); + builder.WithConnectionFactory(format => testConnection.StartAsync(format), connection => ((TestConnection)connection).DisposeAsync()); return builder.Build(); } - private HubConnection CreateHubConnection(Func> connectionFactory) + private HubConnection CreateHubConnection(Func> connectionFactory, Func disposeCallback) { var builder = new HubConnectionBuilder(); - builder.WithConnectionFactory(format => connectionFactory(format)); + builder.WithConnectionFactory(format => connectionFactory(format), disposeCallback); return builder.Build(); } @@ -86,7 +86,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests return new TestConnection().StartAsync(format); } - await AsyncUsing(CreateHubConnection(ConnectionFactory), async connection => + Task DisposeAsync(ConnectionContext connection) + { + return ((TestConnection)connection).DisposeAsync(); + } + + await AsyncUsing(CreateHubConnection(ConnectionFactory, DisposeAsync), async connection => { await connection.StartAsync().OrTimeout(); Assert.Equal(1, createCount); @@ -97,6 +102,41 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests }); } + [Fact] + public async Task StartingDuringStopCreatesANewConnection() + { + // Set up StartAsync to wait on the syncPoint when starting + var createCount = 0; + var onDisposeForFirstConnection = SyncPoint.Create(out var syncPoint); + Task ConnectionFactory(TransferFormat format) + { + createCount += 1; + return new TestConnection(onDispose: createCount == 1 ? onDisposeForFirstConnection : null).StartAsync(format); + } + + Task DisposeAsync(ConnectionContext connection) => ((TestConnection)connection).DisposeAsync(); + + await AsyncUsing(CreateHubConnection(ConnectionFactory, DisposeAsync), async connection => + { + await connection.StartAsync().OrTimeout(); + Assert.Equal(1, createCount); + + var stopTask = connection.StopAsync().OrTimeout(); + + // Wait to hit DisposeAsync on TestConnection (which should be after StopAsync has cleared the connection state) + await syncPoint.WaitForSyncPoint(); + + // We should be able to start now, and StopAsync hasn't completed, nor will it complete while Starting + Assert.False(stopTask.IsCompleted); + await connection.StartAsync().OrTimeout(); + Assert.False(stopTask.IsCompleted); + + // When we release the sync point, the StopAsync task will finish + syncPoint.Continue(); + await stopTask; + }); + } + [Fact] public async Task StartAsyncWithFailedHandshakeCanBeStopped() { diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.Helpers.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.Helpers.cs index 805c69c440..8ec7a17abf 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.Helpers.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.Helpers.cs @@ -11,7 +11,9 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests { await connection.StartAsync(format); return connection; - }); + }, + connecton => ((TestConnection)connection).DisposeAsync()); + if (protocol != null) { builder.WithHubProtocol(protocol); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs index 87fb19c4fd..e9d75828ed 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/HubConnectionTests.cs @@ -41,7 +41,8 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests public async Task ClosedEventRaisedWhenTheClientIsStopped() { var builder = new HubConnectionBuilder(); - builder.WithConnectionFactory(format => new TestConnection().StartAsync(format)); + builder.WithConnectionFactory(format => new TestConnection().StartAsync(format), + connection => ((TestConnection)connection).DisposeAsync()); var hubConnection = builder.Build(); var closedEventTcs = new TaskCompletionSource(); diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/LongPollingTransportTests.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/LongPollingTransportTests.cs index 5d7128392b..a3f150d781 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/LongPollingTransportTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/LongPollingTransportTests.cs @@ -277,7 +277,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests } [Fact] - public async Task LongPollingTransportShutsDownAfterTimeoutEvenIfServerDoesntCompletePoll() + public async Task LongPollingTransportShutsDownImmediatelyEvenIfServerDoesntCompletePoll() { var mockHttpHandler = new Mock(); mockHttpHandler.Protected() @@ -291,7 +291,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests using (var httpClient = new HttpClient(mockHttpHandler.Object)) { var longPollingTransport = new LongPollingTransport(httpClient); - longPollingTransport.ShutdownTimeout = TimeSpan.FromMilliseconds(1); try { @@ -366,6 +365,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests public async Task LongPollingTransportSendsAvailableMessagesWhenTheyArrive() { var sentRequests = new List(); + var tcs = new TaskCompletionSource(); var mockHttpHandler = new Mock(); mockHttpHandler.Protected() @@ -378,12 +378,22 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests // Build a new request object, but convert the entire payload to string sentRequests.Add(await request.Content.ReadAsByteArrayAsync()); } + else if (request.Method == HttpMethod.Get) + { + // This is the poll task + return await tcs.Task; + } + else if (request.Method == HttpMethod.Delete) + { + tcs.TrySetResult(ResponseUtils.CreateResponse(HttpStatusCode.NoContent)); + } return ResponseUtils.CreateResponse(HttpStatusCode.OK); }); using (var httpClient = new HttpClient(mockHttpHandler.Object)) { var longPollingTransport = new LongPollingTransport(httpClient); + try { // Start the transport diff --git a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs index ce0883efb4..65a41af9aa 100644 --- a/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs +++ b/test/Microsoft.AspNetCore.SignalR.Client.Tests/TestConnection.cs @@ -58,8 +58,6 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests Application.Input.OnWriterCompleted((ex, _) => { Application.Output.Complete(); - - _ = DisposeAsync(); }, null); } diff --git a/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs b/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs index 58cba9e36a..aa52bbeb03 100644 --- a/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs +++ b/test/Microsoft.AspNetCore.SignalR.Tests/EndToEndTests.cs @@ -76,10 +76,13 @@ namespace Microsoft.AspNetCore.SignalR.Tests [MemberData(nameof(TransportTypes))] public async Task CanStartAndStopConnectionUsingGivenTransport(HttpTransportType transportType) { - var url = _serverFixture.Url + "/echo"; - var connection = new HttpConnection(new Uri(url), transportType); - await connection.StartAsync(TransferFormat.Text).OrTimeout(); - await connection.DisposeAsync().OrTimeout(); + using (StartLog(out var loggerFactory)) + { + var url = _serverFixture.Url + "/echo"; + var connection = new HttpConnection(new Uri(url), transportType, loggerFactory); + await connection.StartAsync(TransferFormat.Text).OrTimeout(); + await connection.DisposeAsync().OrTimeout(); + } } [ConditionalFact]