Surface fatal exceptions that stop the event loop

- Request an app Shutdown so KestrelEngine gets disposed
- Ensure Listener.Dispose doesn't deadlock before the engine
  can be disposed
- Rely on the existing logic to rethrow in the fatal error when
  the engine gets disposed
This commit is contained in:
Stephen Halter 2015-07-15 16:17:59 -07:00
parent c1ea96d1e0
commit b9901c3bfe
8 changed files with 66 additions and 24 deletions

View File

@ -18,10 +18,12 @@ namespace Kestrel
public class ServerFactory : IServerFactory public class ServerFactory : IServerFactory
{ {
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IApplicationShutdown _appShutdownService;
public ServerFactory(ILibraryManager libraryManager) public ServerFactory(ILibraryManager libraryManager, IApplicationShutdown appShutdownService)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_appShutdownService = appShutdownService;
} }
public IServerInformation Initialize(IConfiguration configuration) public IServerInformation Initialize(IConfiguration configuration)
@ -35,7 +37,7 @@ namespace Kestrel
{ {
var disposables = new List<IDisposable>(); var disposables = new List<IDisposable>();
var information = (ServerInformation)serverInformation; var information = (ServerInformation)serverInformation;
var engine = new KestrelEngine(_libraryManager); var engine = new KestrelEngine(_libraryManager, _appShutdownService);
engine.Start(1); engine.Start(1);
foreach (var address in information.Addresses) foreach (var address in information.Addresses)
{ {

View File

@ -76,22 +76,32 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public void Dispose() public void Dispose()
{ {
var tcs = new TaskCompletionSource<int>(); // Ensure the event loop is still running.
Thread.Post( // If the event loop isn't running and we try to wait on this Post
_ => // to complete, then KestrelEngine will never be disposed and
{ // the exception that stopped the event loop will never be surfaced.
try if (Thread.FatalError == null)
{
var tcs = new TaskCompletionSource<int>();
Thread.Post(
_ =>
{ {
ListenSocket.Dispose(); try
tcs.SetResult(0); {
} ListenSocket.Dispose();
catch (Exception ex) tcs.SetResult(0);
{ }
tcs.SetException(ex); catch (Exception ex)
} {
}, tcs.SetException(ex);
null); }
tcs.Task.Wait(); },
null);
// REVIEW: Should we add a timeout here to be safe?
tcs.Task.Wait();
}
ListenSocket = null; ListenSocket = null;
} }
} }

View File

@ -38,6 +38,7 @@ namespace Microsoft.AspNet.Server.Kestrel
} }
public UvLoopHandle Loop { get { return _loop; } } public UvLoopHandle Loop { get { return _loop; } }
public ExceptionDispatchInfo FatalError { get { return _closeError; } }
public Action<Action<IntPtr>, IntPtr> QueueCloseHandle { get; internal set; } public Action<Action<IntPtr>, IntPtr> QueueCloseHandle { get; internal set; }
@ -173,6 +174,9 @@ namespace Microsoft.AspNet.Server.Kestrel
catch (Exception ex) catch (Exception ex)
{ {
_closeError = ExceptionDispatchInfo.Capture(ex); _closeError = ExceptionDispatchInfo.Capture(ex);
// Request shutdown so we can rethrow this exception
// in Stop which should be observable.
_engine.AppShutdown.RequestShutdown();
} }
} }

View File

@ -13,9 +13,9 @@ namespace Microsoft.AspNet.Server.Kestrel
{ {
public class KestrelEngine : IDisposable public class KestrelEngine : IDisposable
{ {
public KestrelEngine(ILibraryManager libraryManager, IApplicationShutdown appShutdownService)
public KestrelEngine(ILibraryManager libraryManager)
{ {
AppShutdown = appShutdownService;
Threads = new List<KestrelThread>(); Threads = new List<KestrelThread>();
Listeners = new List<Listener>(); Listeners = new List<Listener>();
Memory = new MemoryPool(); Memory = new MemoryPool();
@ -63,6 +63,7 @@ namespace Microsoft.AspNet.Server.Kestrel
public Libuv Libuv { get; private set; } public Libuv Libuv { get; private set; }
public IMemoryPool Memory { get; set; } public IMemoryPool Memory { get; set; }
public IApplicationShutdown AppShutdown { get; private set; }
public List<KestrelThread> Threads { get; private set; } public List<KestrelThread> Threads { get; private set; }
public List<Listener> Listeners { get; private set; } public List<Listener> Listeners { get; private set; }

View File

@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
[Fact] [Fact]
public async Task EngineCanStartAndStop() public async Task EngineCanStartAndStop()
{ {
var engine = new KestrelEngine(LibraryManager); var engine = new KestrelEngine(LibraryManager, new ShutdownNotImplemented());
engine.Start(1); engine.Start(1);
engine.Dispose(); engine.Dispose();
} }
@ -86,7 +86,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
[Fact] [Fact]
public async Task ListenerCanCreateAndDispose() public async Task ListenerCanCreateAndDispose()
{ {
var engine = new KestrelEngine(LibraryManager); var engine = new KestrelEngine(LibraryManager, new ShutdownNotImplemented());
engine.Start(1); engine.Start(1);
var started = engine.CreateServer("http", "localhost", 54321, App); var started = engine.CreateServer("http", "localhost", 54321, App);
started.Dispose(); started.Dispose();
@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
[Fact] [Fact]
public async Task ConnectionCanReadAndWrite() public async Task ConnectionCanReadAndWrite()
{ {
var engine = new KestrelEngine(LibraryManager); var engine = new KestrelEngine(LibraryManager, new ShutdownNotImplemented());
engine.Start(1); engine.Start(1);
var started = engine.CreateServer("http", "localhost", 54321, App); var started = engine.CreateServer("http", "localhost", 54321, App);

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
Libuv _uv; Libuv _uv;
public NetworkingTests() public NetworkingTests()
{ {
var engine = new KestrelEngine(LibraryManager); var engine = new KestrelEngine(LibraryManager, new ShutdownNotImplemented());
_uv = engine.Libuv; _uv = engine.Libuv;
} }

View File

@ -0,0 +1,25 @@
// 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.Threading;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Server.KestrelTests
{
public class ShutdownNotImplemented : IApplicationShutdown
{
public CancellationToken ShutdownRequested
{
get
{
throw new NotImplementedException();
}
}
public void RequestShutdown()
{
throw new NotImplementedException();
}
}
}

View File

@ -45,7 +45,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
public void Create(Func<Frame, Task> app) public void Create(Func<Frame, Task> app)
{ {
_engine = new KestrelEngine(LibraryManager); _engine = new KestrelEngine(LibraryManager, new ShutdownNotImplemented());
_engine.Start(1); _engine.Start(1);
_server = _engine.CreateServer( _server = _engine.CreateServer(
"http", "http",