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

View File

@ -76,22 +76,32 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public void Dispose()
{
var tcs = new TaskCompletionSource<int>();
Thread.Post(
_ =>
{
try
// Ensure the event loop is still running.
// 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.
if (Thread.FatalError == null)
{
var tcs = new TaskCompletionSource<int>();
Thread.Post(
_ =>
{
ListenSocket.Dispose();
tcs.SetResult(0);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
},
null);
tcs.Task.Wait();
try
{
ListenSocket.Dispose();
tcs.SetResult(0);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
},
null);
// REVIEW: Should we add a timeout here to be safe?
tcs.Task.Wait();
}
ListenSocket = null;
}
}

View File

@ -38,6 +38,7 @@ namespace Microsoft.AspNet.Server.Kestrel
}
public UvLoopHandle Loop { get { return _loop; } }
public ExceptionDispatchInfo FatalError { get { return _closeError; } }
public Action<Action<IntPtr>, IntPtr> QueueCloseHandle { get; internal set; }
@ -173,6 +174,9 @@ namespace Microsoft.AspNet.Server.Kestrel
catch (Exception 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 KestrelEngine(ILibraryManager libraryManager)
public KestrelEngine(ILibraryManager libraryManager, IApplicationShutdown appShutdownService)
{
AppShutdown = appShutdownService;
Threads = new List<KestrelThread>();
Listeners = new List<Listener>();
Memory = new MemoryPool();
@ -63,6 +63,7 @@ namespace Microsoft.AspNet.Server.Kestrel
public Libuv Libuv { get; private set; }
public IMemoryPool Memory { get; set; }
public IApplicationShutdown AppShutdown { get; private set; }
public List<KestrelThread> Threads { get; private set; }
public List<Listener> Listeners { get; private set; }

View File

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

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Server.KestrelTests
Libuv _uv;
public NetworkingTests()
{
var engine = new KestrelEngine(LibraryManager);
var engine = new KestrelEngine(LibraryManager, new ShutdownNotImplemented());
_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)
{
_engine = new KestrelEngine(LibraryManager);
_engine = new KestrelEngine(LibraryManager, new ShutdownNotImplemented());
_engine.Start(1);
_server = _engine.CreateServer(
"http",