Make server timeout configurable (#7340)

This commit is contained in:
Mikael Mengistu 2019-02-17 07:57:36 -08:00 committed by David Fowler
parent 8ae4c4dbd6
commit af43b80b1a
6 changed files with 71 additions and 4 deletions

View File

@ -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
{
/// <summary>
/// Gets or sets the interval used by the server to timeout idle connections.
/// </summary>
public TimeSpan? DisconnectTimeout { get; set; }
}
}

View File

@ -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<ConnectionOptions>
{
public static TimeSpan DefaultDisconectTimeout = TimeSpan.FromSeconds(15);
public void Configure(ConnectionOptions options)
{
if (options.DisconnectTimeout == null)
{
options.DisconnectTimeout = DefaultDisconectTimeout;
}
}
}
}

View File

@ -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<IConfigureOptions<ConnectionOptions>, ConnectionOptionsSetup>());
services.TryAddSingleton<HttpConnectionDispatcher>();
services.TryAddSingleton<HttpConnectionManager>();
return services;
}
/// <summary>
/// Adds required services for ASP.NET Core Connection Handlers to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="options">A callback to configure <see cref="ConnectionOptions" /></param>
/// <returns>The same instance of the <see cref="IServiceCollection"/> for chaining.</returns>
public static IServiceCollection AddConnections(this IServiceCollection services, Action<ConnectionOptions> options)
{
return services.Configure(options)
.AddConnections();
}
}
}

View File

@ -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<HttpConnectionManager> _logger;
private readonly ILogger<HttpConnectionContext> _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> connectionOptions)
{
_logger = loggerFactory.CreateLogger<HttpConnectionManager>();
_connectionLogger = loggerFactory.CreateLogger<HttpConnectionContext>();
_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);

View File

@ -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");

View File

@ -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