// 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.Collections.Generic; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Networking; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel { /// /// Summary description for KestrelThread /// public class KestrelThread { // maximum times the work queues swapped and are processed in a single pass // as completing a task may immediately have write data to put on the network // otherwise it needs to wait till the next pass of the libuv loop private const int _maxLoops = 8; private static readonly Action _postCallbackAdapter = (callback, state) => ((Action)callback).Invoke(state); private static readonly Action _postAsyncCallbackAdapter = (callback, state) => ((Action)callback).Invoke(state); private readonly KestrelEngine _engine; private readonly IApplicationLifetime _appLifetime; private readonly Thread _thread; private readonly UvLoopHandle _loop; private readonly UvAsyncHandle _post; private Queue _workAdding = new Queue(1024); private Queue _workRunning = new Queue(1024); private Queue _closeHandleAdding = new Queue(256); private Queue _closeHandleRunning = new Queue(256); private readonly object _workSync = new Object(); private bool _stopImmediate = false; private bool _initCompleted = false; private ExceptionDispatchInfo _closeError; private readonly IKestrelTrace _log; private readonly IThreadPool _threadPool; public KestrelThread(KestrelEngine engine) { _engine = engine; _appLifetime = engine.AppLifetime; _log = engine.Log; _threadPool = engine.ThreadPool; _loop = new UvLoopHandle(_log); _post = new UvAsyncHandle(_log); _thread = new Thread(ThreadStart); _thread.Name = "KestrelThread - libuv"; QueueCloseHandle = PostCloseHandle; QueueCloseAsyncHandle = EnqueueCloseHandle; } public UvLoopHandle Loop { get { return _loop; } } public ExceptionDispatchInfo FatalError { get { return _closeError; } } public Action, IntPtr> QueueCloseHandle { get; } private Action, IntPtr> QueueCloseAsyncHandle { get; } public Task StartAsync() { var tcs = new TaskCompletionSource(); _thread.Start(tcs); return tcs.Task; } // This must be called from the libuv event loop. public void AllowStop() { _post.Unreference(); } public void Stop(TimeSpan timeout) { if (!_initCompleted) { return; } if (_thread.IsAlive) { var stepTimeout = (int)(timeout.TotalMilliseconds / 2); try { Post(t => t.OnStopRude()); if (!_thread.Join(stepTimeout)) { Post(t => t.OnStopImmediate()); if (!_thread.Join(stepTimeout)) { #if NET451 _thread.Abort(); #endif } } } catch (ObjectDisposedException) { // REVIEW: Should we log something here? // Until we rework this logic, ODEs are bound to happen sometimes. if (!_thread.Join(stepTimeout)) { #if NET451 _thread.Abort(); #endif } } } if (_closeError != null) { _closeError.Throw(); } } private void OnStopRude() { Walk(ptr => { var handle = UvMemory.FromIntPtr(ptr); if (handle != _post) { handle.Dispose(); } }); // uv_unref is idempotent so it's OK to call this here and in AllowStop. _post.Unreference(); } private void OnStopImmediate() { _stopImmediate = true; _loop.Stop(); } public void Post(Action callback, object state) { lock (_workSync) { _workAdding.Enqueue(new Work { CallbackAdapter = _postCallbackAdapter, Callback = callback, State = state }); } _post.Send(); } private void Post(Action callback) { Post(thread => callback((KestrelThread)thread), this); } public Task PostAsync(Action callback, object state) { var tcs = new TaskCompletionSource(); lock (_workSync) { _workAdding.Enqueue(new Work { CallbackAdapter = _postAsyncCallbackAdapter, Callback = callback, State = state, Completion = tcs }); } _post.Send(); return tcs.Task; } public void Walk(Action callback) { _engine.Libuv.walk( _loop, (ptr, arg) => { callback(ptr); }, IntPtr.Zero); } private void PostCloseHandle(Action callback, IntPtr handle) { EnqueueCloseHandle(callback, handle); _post.Send(); } private void EnqueueCloseHandle(Action callback, IntPtr handle) { lock (_workSync) { _closeHandleAdding.Enqueue(new CloseHandle { Callback = callback, Handle = handle }); } } private void ThreadStart(object parameter) { var tcs = (TaskCompletionSource)parameter; try { _loop.Init(_engine.Libuv); _post.Init(_loop, OnPost, EnqueueCloseHandle); tcs.SetResult(0); } catch (Exception ex) { tcs.SetException(ex); return; } _initCompleted = true; 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.Dispose(); // Ensure the Dispose operations complete 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. _appLifetime.StopApplication(); } } private void OnPost() { var loopsRemaining = _maxLoops; bool wasWork; do { wasWork = DoPostWork(); wasWork = DoPostCloseHandle() || wasWork; loopsRemaining--; } while (wasWork && loopsRemaining > 0); } private bool DoPostWork() { Queue queue; lock (_workSync) { queue = _workAdding; _workAdding = _workRunning; _workRunning = queue; } bool wasWork = queue.Count > 0; while (queue.Count != 0) { var work = queue.Dequeue(); try { work.CallbackAdapter(work.Callback, work.State); if (work.Completion != null) { _threadPool.Complete(work.Completion); } } catch (Exception ex) { if (work.Completion != null) { _threadPool.Error(work.Completion, ex); } else { _log.LogError(0, ex, "KestrelThread.DoPostWork"); throw; } } } return wasWork; } private bool DoPostCloseHandle() { Queue queue; lock (_workSync) { queue = _closeHandleAdding; _closeHandleAdding = _closeHandleRunning; _closeHandleRunning = queue; } bool wasWork = queue.Count > 0; while (queue.Count != 0) { var closeHandle = queue.Dequeue(); try { closeHandle.Callback(closeHandle.Handle); } catch (Exception ex) { _log.LogError(0, ex, "KestrelThread.DoPostCloseHandle"); throw; } } return wasWork; } private struct Work { public Action CallbackAdapter; public object Callback; public object State; public TaskCompletionSource Completion; } private struct CloseHandle { public Action Callback; public IntPtr Handle; } } }