aspnetcore/src/Microsoft.AspNetCore.Server.../Infrastructure/KestrelThread.cs

345 lines
10 KiB
C#

// 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>
/// Summary description for KestrelThread
/// </summary>
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<object, object> _postCallbackAdapter = (callback, state) => ((Action<object>)callback).Invoke(state);
private static readonly Action<object, object> _postAsyncCallbackAdapter = (callback, state) => ((Action<object>)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<Work> _workAdding = new Queue<Work>(1024);
private Queue<Work> _workRunning = new Queue<Work>(1024);
private Queue<CloseHandle> _closeHandleAdding = new Queue<CloseHandle>(256);
private Queue<CloseHandle> _closeHandleRunning = new Queue<CloseHandle>(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<Action<IntPtr>, IntPtr> QueueCloseHandle { get; }
private Action<Action<IntPtr>, IntPtr> QueueCloseAsyncHandle { get; }
public Task StartAsync()
{
var tcs = new TaskCompletionSource<int>();
_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<UvHandle>(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<object> callback, object state)
{
lock (_workSync)
{
_workAdding.Enqueue(new Work
{
CallbackAdapter = _postCallbackAdapter,
Callback = callback,
State = state
});
}
_post.Send();
}
private void Post(Action<KestrelThread> callback)
{
Post(thread => callback((KestrelThread)thread), this);
}
public Task PostAsync(Action<object> callback, object state)
{
var tcs = new TaskCompletionSource<object>();
lock (_workSync)
{
_workAdding.Enqueue(new Work
{
CallbackAdapter = _postAsyncCallbackAdapter,
Callback = callback,
State = state,
Completion = tcs
});
}
_post.Send();
return tcs.Task;
}
public void Walk(Action<IntPtr> callback)
{
_engine.Libuv.walk(
_loop,
(ptr, arg) =>
{
callback(ptr);
},
IntPtr.Zero);
}
private void PostCloseHandle(Action<IntPtr> callback, IntPtr handle)
{
EnqueueCloseHandle(callback, handle);
_post.Send();
}
private void EnqueueCloseHandle(Action<IntPtr> callback, IntPtr handle)
{
lock (_workSync)
{
_closeHandleAdding.Enqueue(new CloseHandle { Callback = callback, Handle = handle });
}
}
private void ThreadStart(object parameter)
{
var tcs = (TaskCompletionSource<int>)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<Work> 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<CloseHandle> 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<object, object> CallbackAdapter;
public object Callback;
public object State;
public TaskCompletionSource<object> Completion;
}
private struct CloseHandle
{
public Action<IntPtr> Callback;
public IntPtr Handle;
}
}
}