// 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 Microsoft.AspNet.Server.Kestrel.Networking; using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; namespace Microsoft.AspNet.Server.Kestrel { /// /// Summary description for KestrelThread /// public class KestrelThread { KestrelEngine _engine; Thread _thread; UvLoopHandle _loop; UvAsyncHandle _post; Queue _workAdding = new Queue(); Queue _workRunning = new Queue(); Queue _closeHandleAdding = new Queue(); Queue _closeHandleRunning = new Queue(); object _workSync = new Object(); bool _stopImmediate = false; private ExceptionDispatchInfo _closeError; public KestrelThread(KestrelEngine engine) { _engine = engine; _loop = new UvLoopHandle(); _post = new UvAsyncHandle(); _thread = new Thread(ThreadStart); QueueCloseHandle = PostCloseHandle; } public UvLoopHandle Loop { get { return _loop; } } public ExceptionDispatchInfo FatalError { get { return _closeError; } } public Action, IntPtr> QueueCloseHandle { get; internal set; } public Task StartAsync() { var tcs = new TaskCompletionSource(); _thread.Start(tcs); return tcs.Task; } public void Stop(TimeSpan timeout) { Post(OnStop, null); if (!_thread.Join((int)timeout.TotalMilliseconds)) { Post(OnStopRude, null); if (!_thread.Join((int)timeout.TotalMilliseconds)) { Post(OnStopImmediate, null); if (!_thread.Join((int)timeout.TotalMilliseconds)) { #if DNX451 _thread.Abort(); #endif } } } if (_closeError != null) { _closeError.Throw(); } } private void OnStop(object obj) { _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; _loop.Stop(); } public void Post(Action callback, object state) { lock (_workSync) { _workAdding.Enqueue(new Work { Callback = callback, State = state }); } _post.Send(); } public Task PostAsync(Action callback, object state) { var tcs = new TaskCompletionSource(); lock (_workSync) { _workAdding.Enqueue(new Work { Callback = callback, State = state, Completion = tcs }); } _post.Send(); return tcs.Task; } public void Send(Action callback, object state) { if (_loop.ThreadId == Thread.CurrentThread.ManagedThreadId) { callback.Invoke(state); } else { PostAsync(callback, state).Wait(); } } private void PostCloseHandle(Action callback, IntPtr handle) { lock (_workSync) { _closeHandleAdding.Enqueue(new CloseHandle { Callback = callback, Handle = handle }); } _post.Send(); } private void ThreadStart(object parameter) { var tcs = (TaskCompletionSource)parameter; try { _loop.Init(_engine.Libuv); _post.Init(_loop, OnPost); tcs.SetResult(0); } catch (Exception ex) { tcs.SetException(ex); } try { var ran1 = _loop.Run(); if (_stopImmediate) { // thread-abort form of exit, resources will be leaked return; } // 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); if (handle != _post) { handle.Dispose(); } }, IntPtr.Zero); // Ensure the "DangerousClose" operation completes in the event loop. var ran2 = _loop.Run(); _loop.Dispose(); } catch (Exception ex) { _closeError = ExceptionDispatchInfo.Capture(ex); // Request shutdown so we can rethrow this exception // in Stop which should be observable. _engine.AppShutdown.RequestShutdown(); } } private void OnPost() { DoPostWork(); DoPostCloseHandle(); } private void DoPostWork() { Queue queue; lock (_workSync) { queue = _workAdding; _workAdding = _workRunning; _workRunning = queue; } while (queue.Count != 0) { var work = queue.Dequeue(); try { work.Callback(work.State); if (work.Completion != null) { ThreadPool.QueueUserWorkItem( tcs => { ((TaskCompletionSource)tcs).SetResult(0); }, work.Completion); } } catch (Exception ex) { if (work.Completion != null) { ThreadPool.QueueUserWorkItem(_ => work.Completion.SetException(ex), null); } else { Trace.WriteLine("KestrelThread.DoPostWork " + ex.ToString()); } } } } private void DoPostCloseHandle() { Queue queue; lock (_workSync) { queue = _closeHandleAdding; _closeHandleAdding = _closeHandleRunning; _closeHandleRunning = queue; } while (queue.Count != 0) { var closeHandle = queue.Dequeue(); try { closeHandle.Callback(closeHandle.Handle); } catch (Exception ex) { Trace.WriteLine("KestrelThread.DoPostCloseHandle " + ex.ToString()); } } } private struct Work { public Action Callback; public object State; public TaskCompletionSource Completion; } private struct CloseHandle { public Action Callback; public IntPtr Handle; } } }