// 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.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO.Pipelines; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal { public class LibuvThread : PipeScheduler { // 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(TaskCreationOptions.RunContinuationsAsynchronously); 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 _closeHandleSync = new object(); private readonly object _startSync = new object(); private bool _stopImmediate = false; private bool _initCompleted = false; private ExceptionDispatchInfo _closeError; private readonly ILibuvTrace _log; public LibuvThread(LibuvTransport transport) { _transport = transport; _appLifetime = transport.AppLifetime; _log = transport.Log; _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; MemoryPool = new MemoryPool(); WriteReqPool = new WriteReqPool(this, _log); } // For testing public LibuvThread(LibuvTransport transport, int maxLoops) : this(transport) { _maxLoops = maxLoops; } public UvLoopHandle Loop { get { return _loop; } } public MemoryPool MemoryPool { get; } public WriteReqPool WriteReqPool { get; } #if DEBUG public List Requests { get; } = new List(); #endif public ExceptionDispatchInfo FatalError { get { return _closeError; } } public Action, IntPtr> QueueCloseHandle { get; } private Action, IntPtr> QueueCloseAsyncHandle { get; } public Task StartAsync() { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _thread.Start(tcs); return tcs.Task; } public async Task StopAsync(TimeSpan timeout) { lock (_startSync) { if (!_initCompleted) { return; } } Debug.Assert(!_threadTcs.Task.IsCompleted, "The loop thread was completed before calling uv_unref on the post handle."); var stepTimeout = TimeSpan.FromTicks(timeout.Ticks / 3); try { Post(t => t.AllowStop()); if (!await WaitAsync(_threadTcs.Task, stepTimeout).ConfigureAwait(false)) { 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."); } } _closeError?.Throw(); } #if DEBUG private void CheckUvReqLeaks() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); // Detect leaks in UvRequest objects foreach (var request in Requests) { Debug.Assert(request.Target == null, $"{request.Target?.GetType()} object is still alive."); } } #endif 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(); } }); } private void OnStopImmediate() { _stopImmediate = true; _loop.Stop(); } public void Post(Action callback, T state) { var work = new Work { CallbackAdapter = CallbackAdapter.PostCallbackAdapter, Callback = callback, State = state }; lock (_workSync) { _workAdding.Enqueue(work); } _post.Send(); } private void Post(Action callback) { Post(callback, this); } public Task PostAsync(Action callback, T state) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var work = new Work { CallbackAdapter = CallbackAdapter.PostAsyncCallbackAdapter, Callback = callback, State = state, Completion = tcs }; lock (_workSync) { _workAdding.Enqueue(work); } _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) { var closeHandle = new CloseHandle { Callback = callback, Handle = handle }; lock (_closeHandleSync) { _closeHandleAdding.Enqueue(closeHandle); } } 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; } } 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(); // We need this walk because we call ReadStop on on accepted connections when there's back pressure // Calling ReadStop makes the handle as in-active which means the loop can // end while there's still valid handles around. This makes loop.Dispose throw // with an EBUSY. To avoid that, we walk all of the handles and dispose them. Walk(ptr => { var handle = UvMemory.FromIntPtr(ptr); // handle can be null because UvMemory.FromIntPtr looks up a weak reference handle?.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 { MemoryPool.Dispose(); WriteReqPool.Dispose(); _threadTcs.SetResult(null); #if DEBUG // Check for handle leaks after disposing everything CheckUvReqLeaks(); #endif } } 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); work.Completion?.TrySetResult(null); } catch (Exception ex) { if (work.Completion != null) { work.Completion.TrySetException(ex); } else { _log.LogError(0, ex, $"{nameof(LibuvThread)}.{nameof(DoPostWork)}"); throw; } } } return wasWork; } private bool DoPostCloseHandle() { Queue queue; lock (_closeHandleSync) { 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 override void Schedule(Action action) { Post(state => state(), action); } public override void Schedule(Action action, object state) { Post(action, state); } 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); } } }