From af43b80b1a6b9eab454e4313fb6ec2c06726d54a Mon Sep 17 00:00:00 2001 From: Mikael Mengistu Date: Sun, 17 Feb 2019 07:57:36 -0800 Subject: [PATCH] Make server timeout configurable (#7340) --- .../Http.Connections/src/ConnectionOptions.cs | 15 +++++++++++++ .../src/ConnectionOptionsSetup.cs | 21 +++++++++++++++++++ ...onnectionsDependencyInjectionExtensions.cs | 16 ++++++++++++++ .../src/Internal/HttpConnectionManager.cs | 12 ++++++++--- .../test/HttpConnectionDispatcherTests.cs | 10 ++++++++- .../test/HttpConnectionManagerTests.cs | 1 + 6 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 src/SignalR/common/Http.Connections/src/ConnectionOptions.cs create mode 100644 src/SignalR/common/Http.Connections/src/ConnectionOptionsSetup.cs diff --git a/src/SignalR/common/Http.Connections/src/ConnectionOptions.cs b/src/SignalR/common/Http.Connections/src/ConnectionOptions.cs new file mode 100644 index 0000000000..657e51938c --- /dev/null +++ b/src/SignalR/common/Http.Connections/src/ConnectionOptions.cs @@ -0,0 +1,15 @@ +// 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; + +namespace Microsoft.AspNetCore.Http.Connections +{ + public class ConnectionOptions + { + /// + /// Gets or sets the interval used by the server to timeout idle connections. + /// + public TimeSpan? DisconnectTimeout { get; set; } + } +} diff --git a/src/SignalR/common/Http.Connections/src/ConnectionOptionsSetup.cs b/src/SignalR/common/Http.Connections/src/ConnectionOptionsSetup.cs new file mode 100644 index 0000000000..13437aea93 --- /dev/null +++ b/src/SignalR/common/Http.Connections/src/ConnectionOptionsSetup.cs @@ -0,0 +1,21 @@ +// 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 Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Http.Connections +{ + public class ConnectionOptionsSetup : IConfigureOptions + { + public static TimeSpan DefaultDisconectTimeout = TimeSpan.FromSeconds(15); + + public void Configure(ConnectionOptions options) + { + if (options.DisconnectTimeout == null) + { + options.DisconnectTimeout = DefaultDisconectTimeout; + } + } + } +} diff --git a/src/SignalR/common/Http.Connections/src/ConnectionsDependencyInjectionExtensions.cs b/src/SignalR/common/Http.Connections/src/ConnectionsDependencyInjectionExtensions.cs index c77bcda43f..feb78771ca 100644 --- a/src/SignalR/common/Http.Connections/src/ConnectionsDependencyInjectionExtensions.cs +++ b/src/SignalR/common/Http.Connections/src/ConnectionsDependencyInjectionExtensions.cs @@ -1,8 +1,11 @@ // 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 Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Http.Connections.Internal; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection { @@ -20,9 +23,22 @@ namespace Microsoft.Extensions.DependencyInjection { services.AddRouting(); services.AddAuthorizationPolicyEvaluator(); + services.TryAddEnumerable(ServiceDescriptor.Singleton, ConnectionOptionsSetup>()); services.TryAddSingleton(); services.TryAddSingleton(); return services; } + + /// + /// Adds required services for ASP.NET Core Connection Handlers to the specified . + /// + /// The to add services to. + /// A callback to configure + /// The same instance of the for chaining. + public static IServiceCollection AddConnections(this IServiceCollection services, Action options) + { + return services.Configure(options) + .AddConnections(); + } } } diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs index b604efb0eb..231eb8fd87 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionManager.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Buffers.Text; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -15,6 +14,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Http.Connections.Internal { @@ -30,13 +30,19 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal private readonly TimerAwaitable _nextHeartbeat; private readonly ILogger _logger; private readonly ILogger _connectionLogger; + private readonly TimeSpan _disconnectTimeout; public HttpConnectionManager(ILoggerFactory loggerFactory, IApplicationLifetime appLifetime) + : this(loggerFactory, appLifetime, Options.Create(new ConnectionOptions() { DisconnectTimeout = ConnectionOptionsSetup.DefaultDisconectTimeout })) + { + } + + public HttpConnectionManager(ILoggerFactory loggerFactory, IApplicationLifetime appLifetime, IOptions connectionOptions) { _logger = loggerFactory.CreateLogger(); _connectionLogger = loggerFactory.CreateLogger(); _nextHeartbeat = new TimerAwaitable(_heartbeatTickRate, _heartbeatTickRate); - + _disconnectTimeout = connectionOptions.Value.DisconnectTimeout ?? ConnectionOptionsSetup.DefaultDisconectTimeout; // Register these last as the callbacks could run immediately appLifetime.ApplicationStarted.Register(() => Start()); appLifetime.ApplicationStopping.Register(() => CloseConnections()); @@ -155,7 +161,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal // Once the decision has been made to dispose we don't check the status again // But don't clean up connections while the debugger is attached. - if (!Debugger.IsAttached && status == HttpConnectionStatus.Inactive && (DateTimeOffset.UtcNow - lastSeenUtc).TotalSeconds > 5) + if (!Debugger.IsAttached && status == HttpConnectionStatus.Inactive && (DateTimeOffset.UtcNow - lastSeenUtc).TotalSeconds > _disconnectTimeout.TotalSeconds) { Log.ConnectionTimedOut(_logger, connection.ConnectionId); HttpConnectionsEventSource.Log.ConnectionTimedOut(connection.ConnectionId); diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs index 59617e965b..ba2f3c24d8 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionDispatcherTests.cs @@ -25,6 +25,7 @@ using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Moq; using Newtonsoft.Json; @@ -395,7 +396,7 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests { using (StartVerifiableLog()) { - var manager = CreateConnectionManager(LoggerFactory); + var manager = CreateConnectionManager(LoggerFactory, TimeSpan.FromSeconds(5)); var dispatcher = new HttpConnectionDispatcher(manager, LoggerFactory); var connection = manager.CreateConnection(); connection.TransportType = HttpTransportType.LongPolling; @@ -2178,6 +2179,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Tests return new HttpConnectionManager(loggerFactory ?? new LoggerFactory(), new EmptyApplicationLifetime()); } + private static HttpConnectionManager CreateConnectionManager(ILoggerFactory loggerFactory, TimeSpan disconnectTimeout) + { + var connectionOptions = new ConnectionOptions(); + connectionOptions.DisconnectTimeout = disconnectTimeout; + return new HttpConnectionManager(loggerFactory ?? new LoggerFactory(), new EmptyApplicationLifetime(), Options.Create(connectionOptions)); + } + private string GetContentAsString(Stream body) { Assert.True(body.CanSeek, "Can't get content of a non-seekable stream"); diff --git a/src/SignalR/common/Http.Connections/test/HttpConnectionManagerTests.cs b/src/SignalR/common/Http.Connections/test/HttpConnectionManagerTests.cs index 13f569dac7..bd31f0c1b8 100644 --- a/src/SignalR/common/Http.Connections/test/HttpConnectionManagerTests.cs +++ b/src/SignalR/common/Http.Connections/test/HttpConnectionManagerTests.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.Connections.Internal; using Microsoft.AspNetCore.SignalR.Tests; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Xunit; namespace Microsoft.AspNetCore.Http.Connections.Tests