Clean up shutdown management (#59)

* Clean up shutdown management
- ConnectionManager now implements IApplicationEvents. It makes testing cleaner
but makes service registration a little messy.

* Cleaned up service registration and layering a bit
- Added SocketsApplicationLifetimeEvents instead of implementing it
on ConnectionManager directly.
- Exposed ConnectionManager.CloseConnections()
This commit is contained in:
David Fowler 2016-12-05 22:07:19 -08:00 committed by GitHub
parent 28e3c8331b
commit 5f3c1060ab
5 changed files with 48 additions and 32 deletions

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Threading; using System.Threading;
using Microsoft.AspNetCore.Hosting;
namespace Microsoft.AspNetCore.Sockets namespace Microsoft.AspNetCore.Sockets
{ {
@ -14,13 +13,9 @@ namespace Microsoft.AspNetCore.Sockets
private ConcurrentDictionary<string, ConnectionState> _connections = new ConcurrentDictionary<string, ConnectionState>(); private ConcurrentDictionary<string, ConnectionState> _connections = new ConcurrentDictionary<string, ConnectionState>();
private Timer _timer; private Timer _timer;
public ConnectionManager(IApplicationLifetime lifetime) public ConnectionManager()
{ {
_timer = new Timer(Scan, this, 0, 1000); _timer = new Timer(Scan, this, 0, 1000);
// We hook stopping because we need the requests to end, Dispose doesn't work since
// that happens after requests are drained
lifetime.ApplicationStopping.Register(CloseConnections);
} }
public bool TryGetConnection(string id, out ConnectionState state) public bool TryGetConnection(string id, out ConnectionState state)
@ -101,7 +96,7 @@ namespace Microsoft.AspNetCore.Sockets
} }
} }
private void CloseConnections() public void CloseConnections()
{ {
// Stop firing the timer // Stop firing the timer
_timer.Dispose(); _timer.Dispose();

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
namespace Microsoft.AspNetCore.Sockets
{
public class SocketsApplicationLifetimeEvents : IApplicationLifetimeEvents
{
private readonly ConnectionManager _connectionManager;
public SocketsApplicationLifetimeEvents(ConnectionManager connectionManager)
{
_connectionManager = connectionManager;
}
public void OnApplicationStarted()
{
}
public void OnApplicationStopped()
{
}
public void OnApplicationStopping()
{
_connectionManager.CloseConnections();
}
}
}

View File

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Sockets; using Microsoft.AspNetCore.Sockets;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
@ -14,6 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection
{ {
services.AddRouting(); services.AddRouting();
services.TryAddSingleton<ConnectionManager>(); services.TryAddSingleton<ConnectionManager>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IApplicationLifetimeEvents, SocketsApplicationLifetimeEvents>());
services.TryAddSingleton<PipelineFactory>(); services.TryAddSingleton<PipelineFactory>();
services.TryAddSingleton<HttpConnectionDispatcher>(); services.TryAddSingleton<HttpConnectionDispatcher>();
return services; return services;

View File

@ -2,11 +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.IO.Pipelines; using System.IO.Pipelines;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.Extensions.Logging;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Sockets.Tests namespace Microsoft.AspNetCore.Sockets.Tests
@ -16,8 +12,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
[Fact] [Fact]
public void ReservedConnectionsHaveConnectionId() public void ReservedConnectionsHaveConnectionId()
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var connectionManager = new ConnectionManager();
var connectionManager = new ConnectionManager(lifetime);
var state = connectionManager.ReserveConnection(); var state = connectionManager.ReserveConnection();
Assert.NotNull(state.Connection); Assert.NotNull(state.Connection);
@ -30,8 +25,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
[Fact] [Fact]
public void ReservedConnectionsCanBeRetrieved() public void ReservedConnectionsCanBeRetrieved()
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var connectionManager = new ConnectionManager();
var connectionManager = new ConnectionManager(lifetime);
var state = connectionManager.ReserveConnection(); var state = connectionManager.ReserveConnection();
Assert.NotNull(state.Connection); Assert.NotNull(state.Connection);
@ -48,8 +42,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
using (var factory = new PipelineFactory()) using (var factory = new PipelineFactory())
using (var connection = new HttpConnection(factory)) using (var connection = new HttpConnection(factory))
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var connectionManager = new ConnectionManager();
var connectionManager = new ConnectionManager(lifetime);
var state = connectionManager.AddNewConnection(connection); var state = connectionManager.AddNewConnection(connection);
Assert.NotNull(state.Connection); Assert.NotNull(state.Connection);
@ -69,8 +62,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
using (var factory = new PipelineFactory()) using (var factory = new PipelineFactory())
using (var connection = new HttpConnection(factory)) using (var connection = new HttpConnection(factory))
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var connectionManager = new ConnectionManager();
var connectionManager = new ConnectionManager(lifetime);
var state = connectionManager.AddNewConnection(connection); var state = connectionManager.AddNewConnection(connection);
Assert.NotNull(state.Connection); Assert.NotNull(state.Connection);
@ -88,13 +80,12 @@ namespace Microsoft.AspNetCore.Sockets.Tests
} }
[Fact] [Fact]
public async Task ApplicationStoppingClosesConnections() public async Task CloseConnectionsEndsAllPendingConnections()
{ {
using (var factory = new PipelineFactory()) using (var factory = new PipelineFactory())
using (var connection = new HttpConnection(factory)) using (var connection = new HttpConnection(factory))
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var connectionManager = new ConnectionManager();
var connectionManager = new ConnectionManager(lifetime);
var state = connectionManager.AddNewConnection(connection); var state = connectionManager.AddNewConnection(connection);
var task = Task.Run(async () => var task = Task.Run(async () =>
@ -104,7 +95,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
Assert.True(result.IsCompleted); Assert.True(result.IsCompleted);
}); });
lifetime.StopApplication(); connectionManager.CloseConnections();
await task; await task;
} }

View File

@ -23,8 +23,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
[Fact] [Fact]
public async Task GetIdReservesConnectionIdAndReturnsIt() public async Task GetIdReservesConnectionIdAndReturnsIt()
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var manager = new ConnectionManager();
var manager = new ConnectionManager(lifetime);
using (var factory = new PipelineFactory()) using (var factory = new PipelineFactory())
{ {
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null); var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);
@ -45,8 +44,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
[Fact] [Fact]
public async Task SendingToReservedConnectionsThatHaveNotConnectedThrows() public async Task SendingToReservedConnectionsThatHaveNotConnectedThrows()
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var manager = new ConnectionManager();
var manager = new ConnectionManager(lifetime);
var state = manager.ReserveConnection(); var state = manager.ReserveConnection();
using (var factory = new PipelineFactory()) using (var factory = new PipelineFactory())
@ -68,8 +66,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
[Fact] [Fact]
public async Task SendingToUnknownConnectionIdThrows() public async Task SendingToUnknownConnectionIdThrows()
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var manager = new ConnectionManager();
var manager = new ConnectionManager(lifetime);
using (var factory = new PipelineFactory()) using (var factory = new PipelineFactory())
{ {
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null); var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);
@ -89,8 +86,7 @@ namespace Microsoft.AspNetCore.Sockets.Tests
[Fact] [Fact]
public async Task SendingWithoutConnectionIdThrows() public async Task SendingWithoutConnectionIdThrows()
{ {
var lifetime = new ApplicationLifetime(new Logger<ApplicationLifetime>(new LoggerFactory()), Enumerable.Empty<IApplicationLifetimeEvents>()); var manager = new ConnectionManager();
var manager = new ConnectionManager(lifetime);
using (var factory = new PipelineFactory()) using (var factory = new PipelineFactory())
{ {
var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null); var dispatcher = new HttpConnectionDispatcher(manager, factory, loggerFactory: null);