From 31057f65bc671aa1bba853befb6bb4c171987d5b Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 11 Jun 2015 16:56:33 -0700 Subject: [PATCH 1/4] Gracefully shutdown even when there are open connections --- .gitignore | 1 + .../Infrastructure/KestrelThread.cs | 35 +++++++++++++------ .../{UcAsyncHandle.cs => UvAsyncHandle.cs} | 0 .../Networking/UvHandle.cs | 7 ++-- 4 files changed, 30 insertions(+), 13 deletions(-) rename src/Microsoft.AspNet.Server.Kestrel/Networking/{UcAsyncHandle.cs => UvAsyncHandle.cs} (100%) diff --git a/.gitignore b/.gitignore index 5de40f3cd7..0042f1e9e0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ _ReSharper.*/ packages/ artifacts/ PublishProfiles/ +.vs/ *.user *.suo *.cache diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs index 5807898bb2..e24b4e1f12 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs @@ -53,12 +53,16 @@ namespace Microsoft.AspNet.Server.Kestrel Post(OnStop, null); if (!_thread.Join((int)timeout.TotalMilliseconds)) { - Post(OnStopImmediate, null); + Post(OnStopRude, null); if (!_thread.Join((int)timeout.TotalMilliseconds)) { + Post(OnStopImmediate, null); + if (!_thread.Join((int)timeout.TotalMilliseconds)) + { #if DNX451 - _thread.Abort(); + _thread.Abort(); #endif + } } } if (_closeError != null) @@ -72,6 +76,21 @@ namespace Microsoft.AspNet.Server.Kestrel _post.Unreference(); } + private void OnStopRude(object obj) + { + _engine.Libuv.walk( + _loop, + (ptr, arg) => + { + var handle = UvMemory.FromIntPtr(ptr); + if (handle != _post) + { + handle.Dispose(); + } + }, + IntPtr.Zero); + } + private void OnStopImmediate(object obj) { _stopImmediate = true; @@ -133,14 +152,8 @@ namespace Microsoft.AspNet.Server.Kestrel // run the loop one more time to delete the open handles _post.Reference(); _post.DangerousClose(); - _engine.Libuv.walk( - _loop, - (ptr, arg) => - { - var handle = UvMemory.FromIntPtr(ptr); - handle.Dispose(); - }, - IntPtr.Zero); + + // Ensure the "DangerousClose" operation completes in the event loop. var ran2 = _loop.Run(); _loop.Dispose(); @@ -203,7 +216,7 @@ namespace Microsoft.AspNet.Server.Kestrel queue = _closeHandleAdding; _closeHandleAdding = _closeHandleRunning; _closeHandleRunning = queue; - } + } while (queue.Count != 0) { var closeHandle = queue.Dequeue(); diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvAsyncHandle.cs similarity index 100% rename from src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs rename to src/Microsoft.AspNet.Server.Kestrel/Networking/UvAsyncHandle.cs diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs index 98a0f1e077..4c9f23aec1 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs @@ -32,9 +32,12 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking { _uv.close(memory, _destroyMemory); } - else + else if (_queueCloseHandle != null) { - _queueCloseHandle(memory2 => _uv.close(memory2, _destroyMemory), memory); + // This can be called from the finalizer. + // Ensure the closure doesn't reference "this". + var uv = _uv; + _queueCloseHandle(memory2 => uv.close(memory2, _destroyMemory), memory); } } return true; From adc03104916968ef7958fc051f0ddd92f885118c Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 12 Jun 2015 12:49:52 -0700 Subject: [PATCH 2/4] Ensure all handles still get released after graceful shutdown --- .../Infrastructure/KestrelThread.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs index e24b4e1f12..821a8216da 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs @@ -153,6 +153,18 @@ namespace Microsoft.AspNet.Server.Kestrel _post.Reference(); _post.DangerousClose(); + _engine.Libuv.walk( + _loop, + (ptr, arg) => + { + var handle = UvMemory.FromIntPtr(ptr); + if (handle != _post) + { + handle.Dispose(); + } + }, + IntPtr.Zero); + // Ensure the "DangerousClose" operation completes in the event loop. var ran2 = _loop.Run(); From a992c785487271a9c60f079d53e0dc8bf122a70a Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 12 Jun 2015 17:01:33 -0700 Subject: [PATCH 3/4] Dispatch user defined callback so it can't block the event loop --- .../Http/SocketOutput.cs | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs index 9ab5eea89a..531fc65858 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs @@ -4,6 +4,7 @@ using Microsoft.AspNet.Server.Kestrel.Networking; using System; using System.Runtime.InteropServices; +using System.Threading; namespace Microsoft.AspNet.Server.Kestrel.Http { @@ -37,25 +38,17 @@ namespace Microsoft.AspNet.Server.Kestrel.Http var req = new ThisWriteReq(); req.Init(_thread.Loop); req.Contextualize(this, _socket, buffer, callback, state); - _thread.Post(x => - { - ((ThisWriteReq)x).Write(); - }, req); + req.Write(); } public class ThisWriteReq : UvWriteReq { - private static readonly Action _writeCallback = WriteCallback; - private static void WriteCallback(UvWriteReq req, int status, Exception error, object state) - { - ((ThisWriteReq)state).OnWrite(req, status, error); - } - SocketOutput _self; ArraySegment _buffer; UvStreamHandle _socket; Action _callback; object _state; + Exception _callbackError; internal void Contextualize( SocketOutput socketOutput, @@ -73,27 +66,33 @@ namespace Microsoft.AspNet.Server.Kestrel.Http public void Write() { - Write( - _socket, - new ArraySegment>( - new[]{_buffer}), - _writeCallback, - this); + _self._thread.Post(obj => + { + var req = (ThisWriteReq)obj; + req.Write( + req._socket, + new ArraySegment>( + new[] { req._buffer }), + (r, status, error, state) => ((ThisWriteReq)state).OnWrite(status, error), + req); + }, this); } - private void OnWrite(UvWriteReq req, int status, Exception error) + private void OnWrite(int status, Exception error) { KestrelTrace.Log.ConnectionWriteCallback(0, status); //NOTE: pool this? - var callback = _callback; - _callback = null; - var state = _state; - _state = null; - Dispose(); - callback(error, state); - } + + // Get off the event loop before calling user code! + _callbackError = error; + ThreadPool.QueueUserWorkItem(obj => + { + var req = (ThisWriteReq)obj; + req._callback(req._callbackError, req._state); + }, this); + } } From 1592459a0bc53d1b0efc107f4b25ce9e44eb9cc0 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 12 Jun 2015 17:01:48 -0700 Subject: [PATCH 4/4] Dispose Listeners if they fail to start --- .../KestrelEngine.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs b/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs index ae0c87590d..21b4df4523 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs @@ -91,19 +91,33 @@ namespace Microsoft.AspNet.Server.Kestrel public IDisposable CreateServer(string scheme, string host, int port, Func application) { var listeners = new List(); - foreach (var thread in Threads) + + try { - var listener = new Listener(Memory); - listener.StartAsync(scheme, host, port, thread, application).Wait(); - listeners.Add(listener); + foreach (var thread in Threads) + { + var listener = new Listener(Memory); + + listeners.Add(listener); + listener.StartAsync(scheme, host, port, thread, application).Wait(); + } + return new Disposable(() => + { + foreach (var listener in listeners) + { + listener.Dispose(); + } + }); } - return new Disposable(() => + catch { foreach (var listener in listeners) { listener.Dispose(); } - }); + + throw; + } } } }