// 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.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { public class LibuvThread : IScheduler { // 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 readonly int _maxLoops = 8; private readonly LibuvTransport _transport; private readonly IApplicationLifetime _appLifetime; private readonly Thread _thread; private readonly TaskCompletionSource _threadTcs = new TaskCompletionSource(); 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 readonly object _startSync = new object(); private bool _stopImmediate = false; private bool _initCompleted = false; private ExceptionDispatchInfo _closeError; private readonly ILibuvTrace _log; private readonly TimeSpan _shutdownTimeout; public LibuvThread(LibuvTransport transport) { _transport = transport; _appLifetime = transport.AppLifetime; _log = transport.Log; _shutdownTimeout = transport.TransportOptions.ShutdownTimeout; _loop = new UvLoopHandle(_log); _post = new UvAsyncHandle(_log); _thread = new Thread(ThreadStart); _thread.Name = nameof(LibuvThread); #if !DEBUG // Mark the thread as being as unimportant to keeping the process alive. // Don't do this for debug builds, so we know if the thread isn't terminating. _thread.IsBackground = true; #endif QueueCloseHandle = PostCloseHandle; QueueCloseAsyncHandle = EnqueueCloseHandle; PipeFactory = new PipeFactory(); WriteReqPool = new WriteReqPool(this, _log); ConnectionManager = new LibuvConnectionManager(this); } // For testing public LibuvThread(LibuvTransport transport, int maxLoops) : this(transport) { _maxLoops = maxLoops; } public UvLoopHandle Loop { get { return _loop; } } public PipeFactory PipeFactory { get; } public LibuvConnectionManager ConnectionManager { get; } public WriteReqPool WriteReqPool { get; } public ExceptionDispatchInfo FatalError { get { return _closeError; } } public Action, IntPtr> QueueCloseHandle { get; } private Action, IntPtr> QueueCloseAsyncHandle { get; } // The cached result of Loop.Now() which is a timestamp in milliseconds private long Now { get; set; } public Task StartAsync() { var tcs = new TaskCompletionSource(); _thread.Start(tcs); return tcs.Task; } public async Task StopAsync(TimeSpan timeout) { lock (_startSync) { if (!_initCompleted) { return; } } if (!_threadTcs.Task.IsCompleted) { // These operations need to run on the libuv thread so it only makes // sense to attempt execution if it's still running await DisposeConnectionsAsync().ConfigureAwait(false); var stepTimeout = TimeSpan.FromTicks(timeout.Ticks / 3); Post(t => t.AllowStop()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { try { Post(t => t.OnStopRude()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { Post(t => t.OnStopImmediate()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread."); } } } catch (ObjectDisposedException) { if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { _log.LogCritical($"{nameof(LibuvThread)}.{nameof(StopAsync)} failed to terminate libuv thread."); } } } } if (_closeError != null) { _closeError.Throw(); } } private async Task DisposeConnectionsAsync() { // Close and wait for all connections if (!await ConnectionManager.WalkConnectionsAndCloseAsync(_shutdownTimeout).ConfigureAwait(false)) { _log.NotAllConnectionsClosedGracefully(); if (!await ConnectionManager.WalkConnectionsAndAbortAsync(TimeSpan.FromSeconds(1)).ConfigureAwait(false)) { _log.NotAllConnectionsAborted(); } } } private void AllowStop() { _post.Unreference(); } private void OnStopRude() { Walk(ptr => { var handle = UvMemory.FromIntPtr(ptr); if (handle != _post) { // handle can be null because UvMemory.FromIntPtr looks up a weak reference 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, T state) { lock (_workSync) { _workAdding.Enqueue(new Work { CallbackAdapter = CallbackAdapter.PostCallbackAdapter, Callback = callback, State = state }); } _post.Send(); } private void Post(Action callback) { Post(callback, this); } public Task PostAsync(Action callback, T state) { var tcs = new TaskCompletionSource(); lock (_workSync) { _workAdding.Enqueue(new Work { CallbackAdapter = CallbackAdapter.PostAsyncCallbackAdapter, Callback = callback, State = state, Completion = tcs }); } _post.Send(); return tcs.Task; } public void Walk(Action callback) { Walk((ptr, arg) => callback(ptr), IntPtr.Zero); } private void Walk(LibuvFunctions.uv_walk_cb callback, IntPtr arg) { _transport.Libuv.walk( _loop, callback, arg ); } 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) { lock (_startSync) { var tcs = (TaskCompletionSource)parameter; try { _loop.Init(_transport.Libuv); _post.Init(_loop, OnPost, EnqueueCloseHandle); _initCompleted = true; tcs.SetResult(0); } catch (Exception ex) { tcs.SetException(ex); return; } } // This is used to access a 64-bit timestamp (this.Now) using a potentially 32-bit IntPtr. var thisHandle = GCHandle.Alloc(this, GCHandleType.Weak); try { _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. _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(); } finally { PipeFactory.Dispose(); WriteReqPool.Dispose(); thisHandle.Free(); _threadTcs.SetResult(null); } } 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.QueueUserWorkItem(o => { try { ((TaskCompletionSource)o).SetResult(null); } catch (Exception e) { _log.LogError(0, e, $"{nameof(LibuvThread)}.{nameof(DoPostWork)}"); } }, work.Completion); } } catch (Exception ex) { if (work.Completion != null) { ThreadPool.QueueUserWorkItem(o => { try { ((TaskCompletionSource)o).TrySetException(ex); } catch (Exception e) { _log.LogError(0, e, $"{nameof(LibuvThread)}.{nameof(DoPostWork)}"); } }, work.Completion); } else { _log.LogError(0, ex, $"{nameof(LibuvThread)}.{nameof(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, $"{nameof(LibuvThread)}.{nameof(DoPostCloseHandle)}"); throw; } } return wasWork; } private static async Task WaitAsync(Task task, TimeSpan timeout) { return await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false) == task; } public void Schedule(Action action) { Post(state => state(), action); } private struct Work { public Action CallbackAdapter; public object Callback; public object State; public TaskCompletionSource Completion; } private struct CloseHandle { public Action Callback; public IntPtr Handle; } private class CallbackAdapter { public static readonly Action PostCallbackAdapter = (callback, state) => ((Action)callback).Invoke((T)state); public static readonly Action PostAsyncCallbackAdapter = (callback, state) => ((Action)callback).Invoke((T)state); } } }