diff --git a/src/Kestrel/ServerRequest.cs b/src/Kestrel/ServerRequest.cs index 2a7a942ac..e3de59857 100644 --- a/src/Kestrel/ServerRequest.cs +++ b/src/Kestrel/ServerRequest.cs @@ -230,7 +230,7 @@ async Task IHttpUpgradeFeature.UpgradeAsync() _frame.ResponseHeaders["Upgrade"] = values; } } - _frame.ProduceStart(); + await _frame.ProduceStartAsync(); return _frame.DuplexStream; } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs index b159b251f..073cea7b3 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Connection.cs @@ -35,78 +35,80 @@ public interface IConnectionControl { void Pause(); void Resume(); - void End(ProduceEndType endType); + Task EndAsync(ProduceEndType endType); + bool IsInKeepAlive { get; } } public class Connection : ConnectionContext, IConnectionControl { - private static readonly Action _readCallback = ReadCallback; - private static readonly Func _allocCallback = AllocCallback; + private readonly Action _readCallback; + private readonly Func _allocCallback; + private readonly UvTcpStreamHandle _socket; + private readonly Listener _listener; - private static Libuv.uv_buf_t AllocCallback(UvStreamHandle handle, int suggestedSize, object state) - { - return ((Connection)state).OnAlloc(handle, suggestedSize); - } - - private static void ReadCallback(UvStreamHandle handle, int nread, Exception error, object state) - { - ((Connection)state).OnRead(handle, nread, error); - } - - private readonly UvStreamHandle _socket; + private UvReadHandle _read; private Frame _frame; long _connectionId; + private bool _isInKeepAlive; - public Connection(ListenerContext context, UvStreamHandle socket) : base(context) + public Connection(Listener listener, UvTcpStreamHandle socket) : base(listener) { + _readCallback = OnRead; + _allocCallback = OnAlloc; _socket = socket; ConnectionControl = this; - } + _listener = listener; - public void Start() - { KestrelTrace.Log.ConnectionStart(_connectionId); SocketInput = new SocketInput(Memory); SocketOutput = new SocketOutput(Thread, _socket); _frame = new Frame(this); - _socket.ReadStart(_allocCallback, _readCallback, this); + _read = new UvReadHandle(_socket, _allocCallback, _readCallback); + listener.AddConnection(this); } - private Libuv.uv_buf_t OnAlloc(UvStreamHandle handle, int suggestedSize) + private UvBuffer OnAlloc(int suggestedSize) { - return handle.Libuv.buf_init( - SocketInput.Pin(2048), - 2048); + const int bufferSize = 2048; + return new UvBuffer(SocketInput.Pin(bufferSize), bufferSize); } - private void OnRead(UvStreamHandle handle, int status, Exception error) + private void OnRead(int status, Exception error) { SocketInput.Unpin(status); var normalRead = error == null && status > 0; var normalDone = status == 0 || status == -4077 || status == -4095; - var errorDone = !(normalDone || normalRead); - if (normalRead) - { - KestrelTrace.Log.ConnectionRead(_connectionId, status); - } - else if (normalDone || errorDone) + if (!normalRead) { KestrelTrace.Log.ConnectionReadFin(_connectionId); SocketInput.RemoteIntakeFin = true; - _socket.ReadStop(); + if (status != -4095) + { + _read.Dispose(); + _read = null; + _listener.RemoveConnection(this); + _socket.Dispose(); + + // Not sure if this is right + // It should be, but there are some interesting code paths + // while reading the message body regarding status == 0 && RemoteIntakeFin + return; + } - if (errorDone && error != null) + if (!normalDone && error != null) { Trace.WriteLine("Connection.OnRead " + error.ToString()); } } + KestrelTrace.Log.ConnectionRead(_connectionId, status); try { + _isInKeepAlive = false; _frame.Consume(); } catch (Exception ex) @@ -115,56 +117,66 @@ private void OnRead(UvStreamHandle handle, int status, Exception error) } } + bool IConnectionControl.IsInKeepAlive => _isInKeepAlive; + void IConnectionControl.Pause() { KestrelTrace.Log.ConnectionPause(_connectionId); - _socket.ReadStop(); + Debug.Assert(_read != null); + _read.Dispose(); + _read = null; } void IConnectionControl.Resume() { KestrelTrace.Log.ConnectionResume(_connectionId); - _socket.ReadStart(_allocCallback, _readCallback, this); + Debug.Assert(_read == null); + _read = new UvReadHandle(_socket, _allocCallback, _readCallback); } - void IConnectionControl.End(ProduceEndType endType) + async Task IConnectionControl.EndAsync(ProduceEndType endType) { switch (endType) { case ProduceEndType.SocketShutdownSend: KestrelTrace.Log.ConnectionWriteFin(_connectionId, 0); - Thread.Post( - x => - { - KestrelTrace.Log.ConnectionWriteFin(_connectionId, 1); - var self = (Connection)x; - var shutdown = new UvShutdownReq(); - shutdown.Init(self.Thread.Loop); - shutdown.Shutdown(self._socket, (req, status, state) => + await Thread.PostAsync(() => + { + if (_read == null) + return; + + KestrelTrace.Log.ConnectionWriteFin(_connectionId, 1); + new UvShutdownReq( + Thread.Loop, + _socket, + (req, status) => { KestrelTrace.Log.ConnectionWriteFin(_connectionId, 1); - req.Dispose(); - }, null); - }, - this); + // This connection is now done + }); + }); break; case ProduceEndType.ConnectionKeepAlive: KestrelTrace.Log.ConnectionKeepAlive(_connectionId); _frame = new Frame(this); - Thread.Post( - x => ((Frame)x).Consume(), - _frame); + _isInKeepAlive = true; + await Thread.PostAsync(_frame.Consume); break; case ProduceEndType.SocketDisconnect: KestrelTrace.Log.ConnectionDisconnect(_connectionId); - Thread.Post( - x => - { - KestrelTrace.Log.ConnectionStop(_connectionId); - ((UvHandle)x).Dispose(); - }, - _socket); + await Thread.PostAsync(() => + { + _listener.RemoveConnection(this); + if (_read == null) + return; + + _read.Dispose(); + _socket.Dispose(); + KestrelTrace.Log.ConnectionStop(_connectionId); + }); break; + default: + throw new ArgumentException(nameof(endType)); } } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index c20ae236b..7201249a0 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -38,8 +38,8 @@ public FrameContext(ConnectionContext context) : base(context) public interface IFrameControl { - void ProduceContinue(); - void Write(ArraySegment data, Action callback, object state); + Task ProduceContinueAsync(); + Task WriteAsync(ArraySegment data); } public class Frame : FrameContext, IFrameControl @@ -230,21 +230,21 @@ private async Task ExecuteAsync() } finally { - ProduceEnd(error); + await ProduceEndAsync(error); } } - public void Write(ArraySegment data, Action callback, object state) + public async Task WriteAsync(ArraySegment data) { - ProduceStart(); - SocketOutput.Write(data, callback, state); + await ProduceStartAsync(); + await SocketOutput.WriteAsync(data); } - public void Upgrade(IDictionary options, Func callback) + public Task Upgrade(IDictionary options, Func callback) { _keepAlive = false; - ProduceStart(); + return ProduceStartAsync(); // NOTE: needs changes //_upgradeTask = callback(_callContext); @@ -252,31 +252,28 @@ public void Upgrade(IDictionary options, Func call byte[] _continueBytes = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); - public void ProduceContinue() + public Task ProduceContinueAsync() { - if (_resultStarted) return; + if (_resultStarted) + return Task.FromResult(0); string[] expect; if (HttpVersion.Equals("HTTP/1.1") && RequestHeaders.TryGetValue("Expect", out expect) && (expect.FirstOrDefault() ?? "").Equals("100-continue", StringComparison.OrdinalIgnoreCase)) { - SocketOutput.Write( - new ArraySegment(_continueBytes, 0, _continueBytes.Length), - (error, _) => - { - if (error != null) - { - Trace.WriteLine("ProduceContinue " + error.ToString()); - } - }, - null); + return SocketOutput.WriteAsync( + new ArraySegment(_continueBytes, 0, _continueBytes.Length)); } + + return Task.FromResult(0); } - public void ProduceStart() + public async Task ProduceStartAsync() { - if (_resultStarted) return; + if (_resultStarted) + return; + _resultStarted = true; FireOnSendingHeaders(); @@ -286,30 +283,31 @@ public void ProduceStart() var status = ReasonPhrases.ToStatus(StatusCode, ReasonPhrase); var responseHeader = CreateResponseHeader(status, ResponseHeaders); - SocketOutput.Write( - responseHeader.Item1, - (error, x) => - { - if (error != null) - { - Trace.WriteLine("ProduceStart " + error.ToString()); - } - ((IDisposable)x).Dispose(); - }, - responseHeader.Item2); + try + { + await SocketOutput.WriteAsync(responseHeader.Item1); + } + finally + { + responseHeader.Item2.Dispose(); + } } - public void ProduceEnd(Exception ex) + public Task ProduceEndAsync(Exception ex) { - ProduceStart(); + var tasks = new List(3); + tasks.Add(ProduceStartAsync()); if (!_keepAlive) { - ConnectionControl.End(ProduceEndType.SocketShutdownSend); + tasks.Add(ConnectionControl.EndAsync(ProduceEndType.SocketShutdownSend)); } //NOTE: must finish reading request body - ConnectionControl.End(_keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketDisconnect); + tasks.Add(ConnectionControl.EndAsync( + _keepAlive ? ProduceEndType.ConnectionKeepAlive : ProduceEndType.SocketDisconnect)); + + return Task.WhenAll(tasks); } private Tuple, IDisposable> CreateResponseHeader( diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameResponseStream.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameResponseStream.cs index 2ee9f27f3..efbb7a121 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/FrameResponseStream.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/FrameResponseStream.cs @@ -24,23 +24,7 @@ public override void Flush() public override Task FlushAsync(CancellationToken cancellationToken) { - var tcs = new TaskCompletionSource(); - _context.FrameControl.Write( - new ArraySegment(new byte[0]), - (error, arg) => - { - var tcsArg = (TaskCompletionSource)arg; - if (error != null) - { - tcsArg.SetException(error); - } - else - { - tcsArg.SetResult(0); - } - }, - tcs); - return tcs.Task; + return _context.FrameControl.WriteAsync(new ArraySegment(new byte[0])); } public override long Seek(long offset, SeekOrigin origin) @@ -65,23 +49,7 @@ public override void Write(byte[] buffer, int offset, int count) public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - var tcs = new TaskCompletionSource(); - _context.FrameControl.Write( - new ArraySegment(buffer, offset, count), - (error, arg) => - { - var tcsArg = (TaskCompletionSource)arg; - if (error != null) - { - tcsArg.SetException(error); - } - else - { - tcsArg.SetResult(0); - } - }, - tcs); - return tcs.Task; + return _context.FrameControl.WriteAsync(new ArraySegment(buffer, offset, count)); } public override bool CanRead diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs index 5bc7ebdea..18919715d 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Listener.cs @@ -3,7 +3,9 @@ using Microsoft.AspNet.Server.Kestrel.Networking; using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Net; using System.Threading.Tasks; @@ -32,11 +34,12 @@ public ListenerContext(ListenerContext context) /// public class Listener : ListenerContext, IDisposable { - private static readonly Action _connectionCallback = ConnectionCallback; + private readonly Action _connectionCallback; + private readonly List _activeConnections = new List(); - UvTcpHandle ListenSocket { get; set; } + private UvTcpListenHandle _listenSocket; - private static void ConnectionCallback(UvStreamHandle stream, int status, Exception error, object state) + private void ConnectionCallback(int status, Exception error) { if (error != null) { @@ -44,12 +47,13 @@ private static void ConnectionCallback(UvStreamHandle stream, int status, Except } else { - ((Listener)state).OnConnection(stream, status); + OnConnection(status); } } public Listener(IMemoryPool memory) { + _connectionCallback = ConnectionCallback; Memory = memory; } @@ -63,54 +67,54 @@ public Task StartAsync( Thread = thread; Application = application; - var tcs = new TaskCompletionSource(); - Thread.Post(_ => + return Thread.PostAsync(() => { - try - { - ListenSocket = new UvTcpHandle(); - ListenSocket.Init(Thread.Loop, Thread.QueueCloseHandle); - ListenSocket.Bind(new IPEndPoint(IPAddress.Any, port)); - ListenSocket.Listen(10, _connectionCallback, this); - tcs.SetResult(0); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }, null); - return tcs.Task; + _listenSocket = new UvTcpListenHandle( + Thread.Loop, + new IPEndPoint(IPAddress.Any, port), + 10, + _connectionCallback); + }); } - private void OnConnection(UvStreamHandle listenSocket, int status) + private void OnConnection(int status) { - var acceptSocket = new UvTcpHandle(); - acceptSocket.Init(Thread.Loop, Thread.QueueCloseHandle); - listenSocket.Accept(acceptSocket); + var acceptSocket = new UvTcpStreamHandle(Thread.Loop, _listenSocket); - var connection = new Connection(this, acceptSocket); - connection.Start(); + new Connection(this, acceptSocket); + } + + public void AddConnection(Connection c) + { + _activeConnections.Add(c); + } + + public void RemoveConnection(Connection c) + { + _activeConnections.Remove(c); } public void Dispose() { - var tcs = new TaskCompletionSource(); - Thread.Post( - _ => - { - try - { - ListenSocket.Dispose(); - tcs.SetResult(0); - } - catch (Exception ex) - { - tcs.SetException(ex); - } - }, - null); - tcs.Task.Wait(); - ListenSocket = null; + var task = Thread.PostAsync(_listenSocket.Dispose); + task.Wait(); + + var endTasks = new List(); + var copiedConnections = _activeConnections.ToList(); + foreach (var connection in copiedConnections) + { + if (!connection.IsInKeepAlive) + Console.WriteLine("TODO: Warning! Closing an active connection"); + endTasks.Add(connection.EndAsync(ProduceEndType.SocketShutdownSend)); + endTasks.Add(connection.EndAsync(ProduceEndType.SocketDisconnect)); + } + Task.WaitAll(endTasks.ToArray()); + _listenSocket = null; + } + + internal bool IsClean() + { + return _activeConnections.All(x => x.IsInKeepAlive); } } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBodyExchanger.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBodyExchanger.cs index df1dc2930..1d569be4d 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBodyExchanger.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MessageBodyExchanger.cs @@ -71,7 +71,7 @@ public void Transfer(int count, bool fin) } } - public Task ReadAsync(ArraySegment buffer) + public async Task ReadAsync(ArraySegment buffer) { Task result = null; var send100Continue = false; @@ -114,9 +114,9 @@ public Task ReadAsync(ArraySegment buffer) } if (send100Continue) { - _context.FrameControl.ProduceContinue(); + await _context.FrameControl.ProduceContinueAsync(); } - return result; + return await result; } static void CompletePending(object state) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs index 69e5ca2ed..da455da9d 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketInput.cs @@ -106,7 +106,8 @@ public void Unpin(int count) if (_gcHandle.IsAllocated) { _gcHandle.Free(); - Extend(count); + if (count >= 0) + Extend(count); } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs index 9b6e9fa52..39c409900 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs @@ -4,6 +4,7 @@ using Microsoft.AspNet.Server.Kestrel.Networking; using System; using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace Microsoft.AspNet.Server.Kestrel.Http { @@ -12,95 +13,41 @@ namespace Microsoft.AspNet.Server.Kestrel.Http /// public interface ISocketOutput { - void Write(ArraySegment buffer, Action callback, object state); + Task WriteAsync(ArraySegment buffer); } public class SocketOutput : ISocketOutput { private readonly KestrelThread _thread; - private readonly UvStreamHandle _socket; + private readonly UvTcpStreamHandle _socket; - public SocketOutput(KestrelThread thread, UvStreamHandle socket) + public SocketOutput(KestrelThread thread, UvTcpStreamHandle socket) { _thread = thread; _socket = socket; } - public void Write(ArraySegment buffer, Action callback, object state) + public async Task WriteAsync(ArraySegment buffer) { //TODO: need buffering that works var copy = new byte[buffer.Count]; Array.Copy(buffer.Array, buffer.Offset, copy, 0, buffer.Count); - buffer = new ArraySegment(copy); + var arraySegment = new ArraySegment(copy); KestrelTrace.Log.ConnectionWrite(0, buffer.Count); - var req = new ThisWriteReq(); - req.Init(_thread.Loop); - req.Contextualize(this, _socket, buffer, callback, state); - _thread.Post(x => + using (var req = new UvWriteReq( + _thread.Loop, + _socket, + arraySegment)) { - ((ThisWriteReq)x).Write(); - }, req); - } - - public class ThisWriteReq : UvWriteReq - { - private static readonly Action _writeCallback = WriteCallback; - private static void WriteCallback(UvWriteReq req, int status, Exception error, object state) - { - ((ThisWriteReq)state).OnWrite(req, status, error); - } - - SocketOutput _self; - ArraySegment _buffer; - UvStreamHandle _socket; - Action _callback; - object _state; - - internal void Contextualize( - SocketOutput socketOutput, - UvStreamHandle socket, - ArraySegment buffer, - Action callback, - object state) - { - _self = socketOutput; - _socket = socket; - _buffer = buffer; - _callback = callback; - _state = state; - } - - public void Write() - { - Write( - _socket, - new ArraySegment>( - new[]{_buffer}), - _writeCallback, - this); - } - - private void OnWrite(UvWriteReq req, int status, Exception error) - { - KestrelTrace.Log.ConnectionWriteCallback(0, status); - //NOTE: pool this? - - var callback = _callback; - _callback = null; - var state = _state; - _state = null; - - Dispose(); - callback(error, state); + await _thread.PostAsync(req.Write); + await req.Task; } } - public bool Flush(Action drained) { return false; } - } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs index 3b6b885df..42afa3f17 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs @@ -20,27 +20,19 @@ public class KestrelThread Thread _thread; UvLoopHandle _loop; UvAsyncHandle _post; - Queue _workAdding = new Queue(); - Queue _workRunning = new Queue(); - Queue _closeHandleAdding = new Queue(); - Queue _closeHandleRunning = new Queue(); + Queue _workAdding = new Queue(); + Queue _workRunning = 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 Action, IntPtr> QueueCloseHandle { get; internal set; } - public Task StartAsync() { var tcs = new TaskCompletionSource(); @@ -50,16 +42,10 @@ public Task StartAsync() public void Stop(TimeSpan timeout) { - Post(OnStop, null); + Post(OnStop); if (!_thread.Join((int)timeout.TotalMilliseconds)) { - Post(OnStopImmediate, null); - if (!_thread.Join((int)timeout.TotalMilliseconds)) - { -#if DNX451 - _thread.Abort(); -#endif - } + throw new TimeoutException("Loop did not close"); } if (_closeError != null) { @@ -67,167 +53,123 @@ public void Stop(TimeSpan timeout) } } - private void OnStop(object obj) + private void OnStop() { _post.Unreference(); - } + // In a perfect world at this point, there wouldn't be anything left + // that is referenced on the loop � + var postHandle = _post.Handle; + _post.Dispose(); + // � so when returning here, the DestroyMemory callback would be + // executed in the next loop iteration and the loop would exit naturally + + + // However, the world isn't perfect and there are currently ways + // that handles are left that would make the loop run forever. + // Right now from the loop's point of view, _post is still active. + // So we skip _post when we go through the handles that are still active + // and close them manually. + UnsafeNativeMethods.uv_walk( + _loop, + (ptr, arg) => + { + if (ptr != postHandle) + { + UnsafeNativeMethods.uv_close(ptr, null); + } + }, + IntPtr.Zero); + // This does not Dispose() the handles, so for each one + // a nice message is written to the Console by the handle's finalizer - private void OnStopImmediate(object obj) - { - _stopImmediate = true; - _loop.Stop(); + // Now all references are definitely going to be gone + // and the loop will exit after the next iteration } - public void Post(Action callback, object state) + public void Post(Action callback) { lock (_workSync) { - _workAdding.Enqueue(new Work { Callback = callback, State = state }); + _workAdding.Enqueue(callback); } _post.Send(); } - public Task PostAsync(Action callback, object state) + public Task PostAsync(Action callback) { var tcs = new TaskCompletionSource(); - lock (_workSync) + Post(() => { - _workAdding.Enqueue(new Work { Callback = callback, State = state, Completion = tcs }); - } - _post.Send(); + try + { + callback(); + tcs.SetResult(0); + } + catch (Exception e) + { + tcs.SetException(e); + } + }); return tcs.Task; } - private void PostCloseHandle(Action callback, IntPtr handle) + private void ThreadStart(object parameter) { - lock (_workSync) - { - _closeHandleAdding.Enqueue(new CloseHandle { Callback = callback, Handle = handle }); - } - _post.Send(); + var tcs = (TaskCompletionSource)parameter; + SetupLoop(tcs); + RunLoop(); } - private void ThreadStart(object parameter) + private void SetupLoop(TaskCompletionSource tcs) { - var tcs = (TaskCompletionSource)parameter; try { - _loop.Init(_engine.Libuv); - _post.Init(_loop, OnPost); + _loop = new UvLoopHandle(); + _post = new UvAsyncHandle(_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); - handle.Dispose(); - }, - IntPtr.Zero); - var ran2 = _loop.Run(); - - _loop.Dispose(); - } - catch (Exception ex) - { - _closeError = ExceptionDispatchInfo.Capture(ex); - } - } - - private void OnPost() - { - DoPostWork(); - DoPostCloseHandle(); } - private void DoPostWork() + private void RunLoop() { - Queue queue; - lock (_workSync) - { - queue = _workAdding; - _workAdding = _workRunning; - _workRunning = queue; - } - while (queue.Count != 0) + using (_loop) { - var work = queue.Dequeue(); try { - work.Callback(work.State); - if (work.Completion != null) - { - ThreadPool.QueueUserWorkItem( - tcs => - { - ((TaskCompletionSource)tcs).SetResult(0); - }, - work.Completion); - } + _post.Reference(); + _loop.Run(); } catch (Exception ex) { - if (work.Completion != null) - { - ThreadPool.QueueUserWorkItem(_ => work.Completion.SetException(ex), null); - } - else - { - Trace.WriteLine("KestrelThread.DoPostWork " + ex.ToString()); - } + _closeError = ExceptionDispatchInfo.Capture(ex); } } } - private void DoPostCloseHandle() + + private void OnPost() { - Queue queue; - lock (_workSync) - { - queue = _closeHandleAdding; - _closeHandleAdding = _closeHandleRunning; - _closeHandleRunning = queue; - } - while (queue.Count != 0) + var finishedBatch = FinishCurrentBatch(); + foreach (var work in finishedBatch) { - var closeHandle = queue.Dequeue(); - try - { - closeHandle.Callback(closeHandle.Handle); - } - catch (Exception ex) - { - Trace.WriteLine("KestrelThread.DoPostCloseHandle " + ex.ToString()); - } + work(); } + finishedBatch.Clear(); } - private struct Work + private Queue FinishCurrentBatch() { - public Action Callback; - public object State; - public TaskCompletionSource Completion; - } - private struct CloseHandle - { - public Action Callback; - public IntPtr Handle; + Queue queue; + lock (_workSync) + { + queue = _workAdding; + _workAdding = _workRunning; + _workRunning = queue; + } + return queue; } } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/InternalsVisibleTo.cs b/src/Microsoft.AspNet.Server.Kestrel/InternalsVisibleTo.cs new file mode 100644 index 000000000..c9accbb53 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNet.Server.KestrelTests")] diff --git a/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs b/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs index 31861d023..475b25d16 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/KestrelEngine.cs @@ -1,67 +1,74 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using Microsoft.AspNet.Server.Kestrel.Networking; -using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.AspNet.Server.Kestrel.Http; +using Microsoft.AspNet.Server.Kestrel.Networking; using Microsoft.Framework.Runtime; +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Threading.Tasks; namespace Microsoft.AspNet.Server.Kestrel { public class KestrelEngine : IDisposable { + private readonly IDisposable nativeBinder; + private readonly List _listeners = new List(); public KestrelEngine(ILibraryManager libraryManager) { Threads = new List(); Listeners = new List(); Memory = new MemoryPool(); - Libuv = new Libuv(); - var libraryPath = default(string); + var library = libraryManager.GetLibraryInformation("Microsoft.AspNet.Server.Kestrel"); + var libraryPath = library.Path; + if (library.Type == "Project") + { + libraryPath = Path.GetDirectoryName(libraryPath); + } - if (libraryManager != null) + if (Libuv.IsWindows) { - var library = libraryManager.GetLibraryInformation("Microsoft.AspNet.Server.Kestrel"); - libraryPath = library.Path; - if (library.Type == "Project") - { - libraryPath = Path.GetDirectoryName(libraryPath); - } - if (Libuv.IsWindows) - { - var architecture = IntPtr.Size == 4 - ? "x86" - : "amd64"; + var architectureLibraryPath = Path.Combine( + libraryPath, + "native", + "windows", +#if DNXCORE50 + // TODO: This is only temporary. Remove when CoreCLR has a release with the Is64BitProcess member + IntPtr.Size == 8 ? "amd64" : "x86", +#else + Environment.Is64BitProcess ? "amd64" : "x86", +#endif + "libuv.dll"); - libraryPath = Path.Combine( - libraryPath, - "native", - "windows", - architecture, - "libuv.dll"); - } - else if (Libuv.IsDarwin) - { - libraryPath = Path.Combine( - libraryPath, - "native", - "darwin", - "universal", - "libuv.dylib"); - } - else - { - libraryPath = "libuv.so.1"; - } + nativeBinder = new WindowsNativeBinder( + architectureLibraryPath, + typeof(UnsafeNativeMethods)); + } + else if (Libuv.IsDarwin) + { + var architectureLibraryPath = Path.Combine( + libraryPath, + "native", + "darwin", + "universal", + "libuv.dylib" + ); + nativeBinder = new UnixNativeBinder( + architectureLibraryPath, + typeof(UnsafeNativeMethods)); + } + else + { + nativeBinder = new UnixNativeBinder( + "libuv.so.1", + typeof(UnsafeNativeMethods)); } - Libuv.Load(libraryPath); } - public Libuv Libuv { get; private set; } public IMemoryPool Memory { get; set; } public List Threads { get; private set; } public List Listeners { get; private set; } @@ -86,24 +93,30 @@ public void Dispose() thread.Stop(TimeSpan.FromSeconds(2.5)); } Threads.Clear(); + + nativeBinder.Dispose(); } public IDisposable CreateServer(string scheme, string host, int port, Func application) { - var listeners = new List(); foreach (var thread in Threads) { var listener = new Listener(Memory); listener.StartAsync(scheme, host, port, thread, application).Wait(); - listeners.Add(listener); + _listeners.Add(listener); } return new Disposable(() => { - foreach (var listener in listeners) + foreach (var listener in _listeners) { listener.Dispose(); } }); } + + internal bool IsClean() + { + return _listeners.All(x => x.IsClean()); + } } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/Callbacks.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/Callbacks.cs new file mode 100644 index 000000000..e1676bdc5 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/Callbacks.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_close_cb(IntPtr handle); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_async_cb(IntPtr handle); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_connection_cb(IntPtr server, int status); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_alloc_cb(IntPtr server, int suggested_size, out UvBuffer buf); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_read_cb(IntPtr server, int nread, ref UvBuffer buf); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_write_cb(IntPtr req, int status); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_shutdown_cb(IntPtr req, int status); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_walk_cb(IntPtr handle, IntPtr arg); +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/HandleType.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/HandleType.cs new file mode 100644 index 000000000..134508693 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/HandleType.cs @@ -0,0 +1,23 @@ +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public enum HandleType + { + Unknown = 0, + ASYNC, + CHECK, + FS_EVENT, + FS_POLL, + HANDLE, + IDLE, + NAMED_PIPE, + POLL, + PREPARE, + PROCESS, + STREAM, + TCP, + TIMER, + TTY, + UDP, + SIGNAL, + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs index 977fb1826..a8e6df33b 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/Libuv.cs @@ -2,413 +2,73 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Reflection; using System.Runtime.InteropServices; using System.Text; namespace Microsoft.AspNet.Server.Kestrel.Networking { - public class Libuv + public static class Libuv { - public Libuv() + public static bool IsWindows { - IsWindows = PlatformApis.IsWindows(); - if (!IsWindows) + get { - IsDarwin = PlatformApis.IsDarwin(); +#if DNXCORE50 + return true; +#else + var p = (int)Environment.OSVersion.Platform; + return (p != 4) && (p != 6) && (p != 128); +#endif } } - public bool IsWindows; - public bool IsDarwin; + [DllImport("libc", CharSet = CharSet.Ansi)] + static extern int uname([Out] StringBuilder buf); - public Func LoadLibrary; - public Func FreeLibrary; - public Func GetProcAddress; - - public void Load(string dllToLoad) + public static bool IsDarwin { - PlatformApis.Apply(this); - - var module = LoadLibrary(dllToLoad); - - if (module == IntPtr.Zero) + get { - var message = "Unable to load libuv."; - if (!IsWindows && !IsDarwin) + // According to the documentation, + // there might be 9, 33, 65, or 257 bytes + var buffer = new StringBuilder(8192); + try { - // *nix box, so libuv needs to be installed - // TODO: fwlink? - message += " Make sure libuv is installed and available as libuv.so.1"; + if (uname(buffer) == 0) + { + return string.Equals( + buffer.ToString(), + "Darwin", + StringComparison.Ordinal); + } } - - throw new InvalidOperationException(message); - } - - foreach (var field in GetType().GetTypeInfo().DeclaredFields) - { - var procAddress = GetProcAddress(module, field.Name.TrimStart('_')); - if (procAddress == IntPtr.Zero) + catch (Exception) { - continue; } - var value = Marshal.GetDelegateForFunctionPointer(procAddress, field.FieldType); - field.SetValue(this, value); + + return false; } } - public int Check(int statusCode) + public static void ThrowOnError(int statusCode) { - Exception error; - var result = Check(statusCode, out error); + var error = ExceptionForError(statusCode); if (error != null) { throw error; } - return statusCode; } - public int Check(int statusCode, out Exception error) + public static Exception ExceptionForError(int statusCode) { if (statusCode < 0) { - var errorName = err_name(statusCode); - var errorDescription = strerror(statusCode); - error = new Exception("Error " + statusCode + " " + errorName + " " + errorDescription); + var errorName = Marshal.PtrToStringAnsi(UnsafeNativeMethods.uv_err_name(statusCode)); + var errorDescription = Marshal.PtrToStringAnsi(UnsafeNativeMethods.uv_strerror(statusCode)); + return new Exception("Error " + statusCode + " " + errorName + " " + errorDescription); } else - { - error = null; - } - return statusCode; - } - - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_loop_init(UvLoopHandle a0); - uv_loop_init _uv_loop_init; - public void loop_init(UvLoopHandle handle) - { - Check(_uv_loop_init(handle)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_loop_close(IntPtr a0); - uv_loop_close _uv_loop_close; - public void loop_close(UvLoopHandle handle) - { - handle.Validate(closed: true); - Check(_uv_loop_close(handle.InternalGetHandle())); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_run(UvLoopHandle handle, int mode); - uv_run _uv_run; - public int run(UvLoopHandle handle, int mode) - { - handle.Validate(); - return Check(_uv_run(handle, mode)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate void uv_stop(UvLoopHandle handle); - uv_stop _uv_stop; - public void stop(UvLoopHandle handle) - { - handle.Validate(); - _uv_stop(handle); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate void uv_ref(UvHandle handle); - uv_ref _uv_ref; - public void @ref(UvHandle handle) - { - handle.Validate(); - _uv_ref(handle); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate void uv_unref(UvHandle handle); - uv_unref _uv_unref; - public void unref(UvHandle handle) - { - handle.Validate(); - _uv_unref(handle); - } - - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void uv_close_cb(IntPtr handle); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate void uv_close(IntPtr handle, uv_close_cb close_cb); - uv_close _uv_close; - public void close(UvHandle handle, uv_close_cb close_cb) - { - handle.Validate(closed: true); - _uv_close(handle.InternalGetHandle(), close_cb); - } - public void close(IntPtr handle, uv_close_cb close_cb) - { - _uv_close(handle, close_cb); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void uv_async_cb(IntPtr handle); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_async_init(UvLoopHandle loop, UvAsyncHandle handle, uv_async_cb cb); - uv_async_init _uv_async_init; - public void async_init(UvLoopHandle loop, UvAsyncHandle handle, uv_async_cb cb) - { - loop.Validate(); - handle.Validate(); - Check(_uv_async_init(loop, handle, cb)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_async_send(UvAsyncHandle handle); - uv_async_send _uv_async_send; - public void async_send(UvAsyncHandle handle) - { - Check(_uv_async_send(handle)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_tcp_init(UvLoopHandle loop, UvTcpHandle handle); - uv_tcp_init _uv_tcp_init; - public void tcp_init(UvLoopHandle loop, UvTcpHandle handle) - { - loop.Validate(); - handle.Validate(); - Check(_uv_tcp_init(loop, handle)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags); - uv_tcp_bind _uv_tcp_bind; - public void tcp_bind(UvTcpHandle handle, ref sockaddr addr, int flags) - { - handle.Validate(); - Check(_uv_tcp_bind(handle, ref addr, flags)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void uv_connection_cb(IntPtr server, int status); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_listen(UvStreamHandle handle, int backlog, uv_connection_cb cb); - uv_listen _uv_listen; - public void listen(UvStreamHandle handle, int backlog, uv_connection_cb cb) - { - handle.Validate(); - Check(_uv_listen(handle, backlog, cb)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_accept(UvStreamHandle server, UvStreamHandle client); - uv_accept _uv_accept; - public void accept(UvStreamHandle server, UvStreamHandle client) - { - server.Validate(); - client.Validate(); - Check(_uv_accept(server, client)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void uv_alloc_cb(IntPtr server, int suggested_size, out uv_buf_t buf); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void uv_read_cb(IntPtr server, int nread, ref uv_buf_t buf); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_read_start(UvStreamHandle handle, uv_alloc_cb alloc_cb, uv_read_cb read_cb); - uv_read_start _uv_read_start; - public void read_start(UvStreamHandle handle, uv_alloc_cb alloc_cb, uv_read_cb read_cb) - { - handle.Validate(); - Check(_uv_read_start(handle, alloc_cb, read_cb)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_read_stop(UvStreamHandle handle); - uv_read_stop _uv_read_stop; - public void read_stop(UvStreamHandle handle) - { - handle.Validate(); - Check(_uv_read_stop(handle)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_try_write(UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs); - uv_try_write _uv_try_write; - public int try_write(UvStreamHandle handle, Libuv.uv_buf_t[] bufs, int nbufs) - { - handle.Validate(); - return Check(_uv_try_write(handle, bufs, nbufs)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void uv_write_cb(IntPtr req, int status); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - unsafe delegate int uv_write(UvWriteReq req, UvStreamHandle handle, Libuv.uv_buf_t* bufs, int nbufs, uv_write_cb cb); - uv_write _uv_write; - unsafe public void write(UvWriteReq req, UvStreamHandle handle, Libuv.uv_buf_t* bufs, int nbufs, uv_write_cb cb) - { - req.Validate(); - handle.Validate(); - Check(_uv_write(req, handle, bufs, nbufs, cb)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void uv_shutdown_cb(IntPtr req, int status); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_shutdown(UvShutdownReq req, UvStreamHandle handle, uv_shutdown_cb cb); - uv_shutdown _uv_shutdown; - public void shutdown(UvShutdownReq req, UvStreamHandle handle, uv_shutdown_cb cb) - { - req.Validate(); - handle.Validate(); - Check(_uv_shutdown(req, handle, cb)); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate IntPtr uv_err_name(int err); - uv_err_name _uv_err_name; - public unsafe String err_name(int err) - { - IntPtr ptr = _uv_err_name(err); - return ptr == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(ptr); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate IntPtr uv_strerror(int err); - uv_strerror _uv_strerror; - public unsafe String strerror(int err) - { - IntPtr ptr = _uv_strerror(err); - return ptr == IntPtr.Zero ? null : Marshal.PtrToStringAnsi(ptr); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_loop_size(); - uv_loop_size _uv_loop_size; - public int loop_size() - { - return _uv_loop_size(); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_handle_size(HandleType handleType); - uv_handle_size _uv_handle_size; - public int handle_size(HandleType handleType) - { - return _uv_handle_size(handleType); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_req_size(RequestType reqType); - uv_req_size _uv_req_size; - public int req_size(RequestType reqType) - { - return _uv_req_size(reqType); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_ip4_addr(string ip, int port, out sockaddr addr); - - uv_ip4_addr _uv_ip4_addr; - public int ip4_addr(string ip, int port, out sockaddr addr, out Exception error) - { - return Check(_uv_ip4_addr(ip, port, out addr), out error); + return null; } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - delegate int uv_ip6_addr(string ip, int port, out sockaddr addr); - - uv_ip6_addr _uv_ip6_addr; - public int ip6_addr(string ip, int port, out sockaddr addr, out Exception error) - { - return Check(_uv_ip6_addr(ip, port, out addr), out error); - } - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate void uv_walk_cb(IntPtr handle, IntPtr arg); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - unsafe delegate int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg); - uv_walk _uv_walk; - unsafe public void walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg) - { - loop.Validate(); - _uv_walk(loop, walk_cb, arg); - } - - public uv_buf_t buf_init(IntPtr memory, int len) - { - return new uv_buf_t(memory, len, IsWindows); - } - - public struct sockaddr - { - long x0; - long x1; - long x2; - long x3; - } - - public struct uv_buf_t - { - public uv_buf_t(IntPtr memory, int len, bool IsWindows) - { - if (IsWindows) - { - x0 = (IntPtr)len; - x1 = memory; - } - else - { - x0 = memory; - x1 = (IntPtr)len; - } - } - - public IntPtr x0; - public IntPtr x1; - } - - public enum HandleType - { - Unknown = 0, - ASYNC, - CHECK, - FS_EVENT, - FS_POLL, - HANDLE, - IDLE, - NAMED_PIPE, - POLL, - PREPARE, - PROCESS, - STREAM, - TCP, - TIMER, - TTY, - UDP, - SIGNAL, - } - - public enum RequestType - { - Unknown = 0, - REQ, - CONNECT, - WRITE, - SHUTDOWN, - UDP_SEND, - FS, - WORK, - GETADDRINFO, - GETNAMEINFO, - } - //int handle_size_async; - //int handle_size_tcp; - //int req_size_write; - //int req_size_shutdown; } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/PlatformApis.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/PlatformApis.cs deleted file mode 100644 index 55a2fe77b..000000000 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/PlatformApis.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Reflection; -using System.Runtime.InteropServices; -using System.Text; - -namespace Microsoft.AspNet.Server.Kestrel.Networking -{ - public static class PlatformApis - { - public static bool IsWindows() - { -#if DNXCORE50 - return true; -#else - var p = (int)Environment.OSVersion.Platform; - return (p != 4) && (p != 6) && (p != 128); -#endif - } - - [DllImport("libc")] - static extern int uname(IntPtr buf); - - static unsafe string GetUname() - { - var buffer = new byte[8192]; - try - { - fixed (byte* buf = buffer) - { - if (uname((IntPtr)buf) == 0) - { - return Marshal.PtrToStringAnsi((IntPtr)buf); - } - } - return string.Empty; - } - catch - { - return string.Empty; - } - } - - public static bool IsDarwin() - { - return string.Equals(GetUname(), "Darwin", StringComparison.Ordinal); - } - - public static void Apply(Libuv libuv) - { - if (libuv.IsWindows) - { - WindowsApis.Apply(libuv); - } - else - { - LinuxApis.Apply(libuv); - } - } - - public static class WindowsApis - { - [DllImport("kernel32")] - public static extern IntPtr LoadLibrary(string dllToLoad); - - [DllImport("kernel32")] - public static extern bool FreeLibrary(IntPtr hModule); - - [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] - public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); - - public static void Apply(Libuv libuv) - { - libuv.LoadLibrary = LoadLibrary; - libuv.FreeLibrary = FreeLibrary; - libuv.GetProcAddress = GetProcAddress; - } - } - - public static class LinuxApis - { - [DllImport("libdl")] - public static extern IntPtr dlopen(String fileName, int flags); - - [DllImport("libdl")] - public static extern IntPtr dlsym(IntPtr handle, String symbol); - - [DllImport("libdl")] - public static extern int dlclose(IntPtr handle); - - [DllImport("libdl")] - public static extern IntPtr dlerror(); - - public static IntPtr LoadLibrary(string dllToLoad) - { - return dlopen(dllToLoad, 2); - } - - public static bool FreeLibrary(IntPtr hModule) - { - return dlclose(hModule) == 0; - } - - public static IntPtr GetProcAddress(IntPtr hModule, string procedureName) - { - dlerror(); - var res = dlsym(hModule, procedureName); - var errPtr = dlerror(); - return errPtr == IntPtr.Zero ? res : IntPtr.Zero; - } - - public static void Apply(Libuv libuv) - { - libuv.LoadLibrary = LoadLibrary; - libuv.FreeLibrary = FreeLibrary; - libuv.GetProcAddress = GetProcAddress; - } - } - } -} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/RequestType.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/RequestType.cs new file mode 100644 index 000000000..d545df39b --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/RequestType.cs @@ -0,0 +1,16 @@ +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public enum RequestType + { + Unknown = 0, + REQ, + CONNECT, + WRITE, + SHUTDOWN, + UDP_SEND, + FS, + WORK, + GETADDRINFO, + GETNAMEINFO, + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/Sockaddr.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/Sockaddr.cs new file mode 100644 index 000000000..949d37783 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/Sockaddr.cs @@ -0,0 +1,11 @@ +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public struct Sockaddr + { +#pragma warning disable 169 + long x0; + long x1; + long x2; + long x3; + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs deleted file mode 100644 index 31e8f9b05..000000000 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UcAsyncHandle.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; - -namespace Microsoft.AspNet.Server.Kestrel.Networking -{ - public class UvAsyncHandle : UvHandle - { - private static Libuv.uv_async_cb _uv_async_cb = AsyncCb; - private Action _callback; - - public void Init(UvLoopHandle loop, Action callback) - { - CreateMemory( - loop.Libuv, - loop.ThreadId, - loop.Libuv.handle_size(Libuv.HandleType.ASYNC)); - - _callback = callback; - _uv.async_init(loop, this, _uv_async_cb); - } - - public void DangerousClose() - { - Dispose(); - ReleaseHandle(); - } - - public void Send() - { - _uv.async_send(this); - } - - unsafe static void AsyncCb(IntPtr handle) - { - FromIntPtr(handle)._callback.Invoke(); - } - } -} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UnixNativeBinder.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UnixNativeBinder.cs new file mode 100644 index 000000000..7b7f3a31a --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UnixNativeBinder.cs @@ -0,0 +1,49 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public sealed class UnixNativeBinder : IDisposable + { + private readonly IntPtr handle; + + public UnixNativeBinder(string soFile, Type bindTarget) + { + handle = dlopen(soFile, 2); + if (handle == IntPtr.Zero) + { + throw new DllNotFoundException(soFile); + } + + foreach (var field in bindTarget.GetTypeInfo().DeclaredFields) + { + dlerror(); + var pointer = dlsym(handle, field.Name); + var error = dlerror(); + if (error != IntPtr.Zero) + { + throw new InvalidOperationException("Could not load member: " + field.Name); + } + + var value = Marshal.GetDelegateForFunctionPointer(pointer, field.FieldType); + field.SetValue(this, value); + } + } + + public void Dispose() + { + dlclose(handle); + GC.SuppressFinalize(this); + } + + [DllImport("dl")] + private static extern IntPtr dlopen(string fileName, int flags); + [DllImport("dl")] + private static extern IntPtr dlsym(IntPtr handle, string symbol); + [DllImport("dl")] + private static extern int dlclose(IntPtr handle); + [DllImport("dl")] + private static extern IntPtr dlerror(); + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UnsafeNativeMethods.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UnsafeNativeMethods.cs new file mode 100644 index 000000000..2868b25cd --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UnsafeNativeMethods.cs @@ -0,0 +1,112 @@ +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + internal static class UnsafeNativeMethods + { +#pragma warning disable 649 + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_loop_init_delegate(UvLoopHandle handle); + public static uv_loop_init_delegate uv_loop_init; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_loop_close_delegate(UvLoopHandle handle); + public static uv_loop_close_delegate uv_loop_close; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_run_delegate(UvLoopHandle handle, int mode); + public static uv_run_delegate uv_run; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_ref_delegate(IntPtr handle); + public static uv_ref_delegate uv_ref; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_unref_delegate(IntPtr handle); + public static uv_unref_delegate uv_unref; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void uv_close_delegate(IntPtr handle, uv_close_cb close_cb); + public static uv_close_delegate uv_close; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_async_init_delegate(UvLoopHandle loop, IntPtr handle, uv_async_cb cb); + public static uv_async_init_delegate uv_async_init; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_async_send_delegate(IntPtr handle); + public static uv_async_send_delegate uv_async_send; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_tcp_init_delegate(UvLoopHandle loop, IntPtr handle); + public static uv_tcp_init_delegate uv_tcp_init; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_tcp_bind_delegate(IntPtr handle, ref Sockaddr addr, int flags); + public static uv_tcp_bind_delegate uv_tcp_bind; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_listen_delegate(IntPtr handle, int backlog, uv_connection_cb cb); + public static uv_listen_delegate uv_listen; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_accept_delegate(IntPtr server, IntPtr client); + public static uv_accept_delegate uv_accept; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_read_start_delegate(IntPtr handle, uv_alloc_cb alloc_cb, uv_read_cb read_cb); + public static uv_read_start_delegate uv_read_start; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_read_stop_delegate(IntPtr handle); + public static uv_read_stop_delegate uv_read_stop; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_write_delegate(UvWriteReq req, IntPtr handle, UvBuffer[] bufs, int nbufs, uv_write_cb cb); + public static uv_write_delegate uv_write; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_shutdown_delegate(UvShutdownReq req, IntPtr handle, uv_shutdown_cb cb); + public static uv_shutdown_delegate uv_shutdown; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + // Cannot use [return: MarshalAs(UnmanagedType.LPStr)] + // because the source const char* must not be freed, + // which the marshaling does + public delegate IntPtr uv_err_name_delegate(int err); + public static uv_err_name_delegate uv_err_name; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + // Cannot use [return: MarshalAs(UnmanagedType.LPStr)] + // because the source const char* must not be freed, + // which the marshaling does + public delegate IntPtr uv_strerror_delegate(int err); + public static uv_strerror_delegate uv_strerror; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_loop_size_delegate(); + public static uv_loop_size_delegate uv_loop_size; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_handle_size_delegate(HandleType handleType); + public static uv_handle_size_delegate uv_handle_size; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_req_size_delegate(RequestType reqType); + public static uv_req_size_delegate uv_req_size; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_ip4_addr_delegate(string ip, int port, out Sockaddr addr); + public static uv_ip4_addr_delegate uv_ip4_addr; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_ip6_addr_delegate(string ip, int port, out Sockaddr addr); + public static uv_ip6_addr_delegate uv_ip6_addr; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int uv_walk_delegate(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg); + public static uv_walk_delegate uv_walk; + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvAsyncHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvAsyncHandle.cs new file mode 100644 index 000000000..4336d9ea2 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvAsyncHandle.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public class UvAsyncHandle : UvLoopResource + { + private readonly uv_async_cb _uv_async_cb; + private readonly Action _callback; + + public UvAsyncHandle( + UvLoopHandle loop, + Action callback) + : base(loop.ThreadId, GetSize()) + { + _uv_async_cb = AsyncCb; + _callback = callback; + loop.Validate(); + Validate(); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_async_init(loop, Handle, _uv_async_cb)); + } + + private static int GetSize() + { + return UnsafeNativeMethods.uv_handle_size(HandleType.ASYNC); + } + + public void Send() + { + Libuv.ThrowOnError(UnsafeNativeMethods.uv_async_send(Handle)); + } + + private void AsyncCb(IntPtr handle) + { + _callback.Invoke(); + } + + public void Reference() + { + Validate(); + UnsafeNativeMethods.uv_ref(Handle); + } + + public void Unreference() + { + Validate(); + UnsafeNativeMethods.uv_unref(Handle); + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvBuffer.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvBuffer.cs new file mode 100644 index 000000000..83db158f8 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvBuffer.cs @@ -0,0 +1,24 @@ +using System; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public struct UvBuffer + { + public UvBuffer(IntPtr memory, int len) + { + if (Libuv.IsWindows) + { + x0 = (IntPtr)len; + x1 = memory; + } + else + { + x0 = memory; + x1 = (IntPtr)len; + } + } + + public IntPtr x0; // Win: ULONG, Linux: char* + public IntPtr x1; // Win: char*, Linux: size_t + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs deleted file mode 100644 index fe12f1933..000000000 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvHandle.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Threading; - -namespace Microsoft.AspNet.Server.Kestrel.Networking -{ - public abstract class UvHandle : UvMemory - { - static Libuv.uv_close_cb _destroyMemory = DestroyMemory; - Action, IntPtr> _queueCloseHandle; - - unsafe protected void CreateHandle( - Libuv uv, - int threadId, - int size, - Action, IntPtr> queueCloseHandle) - { - _queueCloseHandle = queueCloseHandle; - CreateMemory(uv, threadId, size); - } - - protected override bool ReleaseHandle() - { - var memory = handle; - if (memory != IntPtr.Zero) - { - handle = IntPtr.Zero; - - if (Thread.CurrentThread.ManagedThreadId == ThreadId) - { - _uv.close(memory, _destroyMemory); - } - else - { - _queueCloseHandle(memory2 => _uv.close(memory2, _destroyMemory), memory); - } - } - return true; - } - - public void Reference() - { - _uv.@ref(this); - } - - public void Unreference() - { - _uv.unref(this); - } - } -} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs index a13ae2ccc..887e23fab 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopHandle.cs @@ -1,44 +1,35 @@ // Copyright (c) Microsoft Open Technologies, Inc. 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.Threading; namespace Microsoft.AspNet.Server.Kestrel.Networking { - public class UvLoopHandle : UvHandle + public class UvLoopHandle : UvMemoryResource { - public void Init(Libuv uv) + public UvLoopHandle() + : base(Thread.CurrentThread.ManagedThreadId, GetSize()) { - CreateMemory( - uv, - Thread.CurrentThread.ManagedThreadId, - uv.loop_size()); - - _uv.loop_init(this); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_loop_init(this)); } - public int Run(int mode = 0) + private static int GetSize() { - return _uv.run(this, mode); + return UnsafeNativeMethods.uv_loop_size(); } - public void Stop() + public void Run(int mode = 0) { - _uv.stop(this); + Validate(); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_run(this, mode)); } - protected override bool ReleaseHandle() + protected override void Dispose(bool disposing) { - var memory = this.handle; - if (memory != IntPtr.Zero) - { - _uv.loop_close(this); - handle = IntPtr.Zero; - DestroyMemory(memory); - } - return true; - } + if (disposing) + Libuv.ThrowOnError(UnsafeNativeMethods.uv_loop_close(this)); + base.Dispose(disposing); + } } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopResource.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopResource.cs new file mode 100644 index 000000000..4575fb88d --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvLoopResource.cs @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public abstract class UvLoopResource : IDisposable + { + private static readonly uv_close_cb _destroyMemory = DestroyMemory; + + private readonly int _threadId; + + protected UvLoopResource(int threadId, int size) + { + _threadId = threadId; + + Handle = Marshal.AllocCoTaskMem(size); + } + + public IntPtr Handle { get; private set; } + + public void Validate() + { + Trace.Assert(_threadId == Thread.CurrentThread.ManagedThreadId, "ThreadId is incorrect"); + if (Handle == IntPtr.Zero) + throw new ObjectDisposedException(GetType().Name); + } + + private static void DestroyMemory(IntPtr memory) + { + Marshal.FreeCoTaskMem(memory); + } + + public void Dispose() + { + if (Handle == IntPtr.Zero) + throw new ObjectDisposedException(nameof(UvLoopResource)); + Validate(); + Dispose(true); + GC.SuppressFinalize(this); + } + + ~UvLoopResource() + { + // If the finalizer is called, this means Dispose was not. + // In that case, there is no way to know if the event loop is still active + // and so uv_close cannot be reliably expected to call back. + // However, the code still frees the unmanaged resources, + // so expect something to blow up later. + + // There is one common case of this happening, + // namely when handles need to be manually closed during shutdown + + Console.WriteLine("TODO: Warning! UvHandle was finalized instead of disposed. This is either a bug in Kestrel (unlikely) or some other code didn't close all resources before Kestrel was stopped."); + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // After this call, the managed object is ready for collection. + // The unmanaged memory is being passed to the callback to free + UnsafeNativeMethods.uv_close(Handle, _destroyMemory); + } + else + { + DestroyMemory(Handle); + } + + Handle = IntPtr.Zero; + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs deleted file mode 100644 index c3b3c6ce2..000000000 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemory.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -#define TRACE -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Microsoft.AspNet.Server.Kestrel.Networking -{ - /// - /// Summary description for UvMemory - /// - public abstract class UvMemory : SafeHandle - { - protected Libuv _uv; - private int _threadId; - - public UvMemory() : base(IntPtr.Zero, true) - { - } - - public Libuv Libuv { get { return _uv; } } - - public override bool IsInvalid - { - get - { - return handle == IntPtr.Zero; - } - } - - public int ThreadId - { - get - { - return _threadId; - } - private set - { - _threadId = value; - } - } - - unsafe protected void CreateMemory(Libuv uv, int threadId, int size) - { - _uv = uv; - ThreadId = threadId; - - handle = Marshal.AllocCoTaskMem(size); - *(IntPtr*)handle = GCHandle.ToIntPtr(GCHandle.Alloc(this, GCHandleType.Weak)); - } - - unsafe protected static void DestroyMemory(IntPtr memory) - { - var gcHandlePtr = *(IntPtr*)memory; - if (gcHandlePtr != IntPtr.Zero) - { - var gcHandle = GCHandle.FromIntPtr(gcHandlePtr); - gcHandle.Free(); - } - Marshal.FreeCoTaskMem(memory); - } - - internal IntPtr InternalGetHandle() - { - return handle; - } - - public void Validate(bool closed = false) - { - Trace.Assert(closed || !IsClosed, "Handle is closed"); - Trace.Assert(!IsInvalid, "Handle is invalid"); - Trace.Assert(_threadId == Thread.CurrentThread.ManagedThreadId, "ThreadId is incorrect"); - } - - unsafe public static THandle FromIntPtr(IntPtr handle) - { - GCHandle gcHandle = GCHandle.FromIntPtr(*(IntPtr*)handle); - return (THandle)gcHandle.Target; - } - - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemoryResource.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemoryResource.cs new file mode 100644 index 000000000..26338748e --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvMemoryResource.cs @@ -0,0 +1,39 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public abstract class UvMemoryResource : SafeHandle + { + private readonly int _threadId; + private readonly GCHandle _selfKeepAlive; + + public UvMemoryResource(int threadId, int size) + : base(IntPtr.Zero, true) + { + _threadId = threadId; + + SetHandle(Marshal.AllocCoTaskMem(size)); + _selfKeepAlive = GCHandle.Alloc(this, GCHandleType.Normal); + } + + public override bool IsInvalid => handle == IntPtr.Zero; + public int ThreadId => _threadId; + + public void Validate() + { + Trace.Assert(!IsInvalid, "Handle is invalid"); + Trace.Assert(_threadId == Thread.CurrentThread.ManagedThreadId, "ThreadId is incorrect"); + } + + protected override bool ReleaseHandle() + { + _selfKeepAlive.Free(); + Marshal.FreeCoTaskMem(handle); + handle = IntPtr.Zero; + return true; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvReadHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvReadHandle.cs new file mode 100644 index 000000000..b12d0be3d --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvReadHandle.cs @@ -0,0 +1,77 @@ +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public sealed class UvReadHandle : IDisposable + { + private readonly uv_read_cb _uv_read_cb; + private readonly uv_alloc_cb _uv_alloc_cb; + + private readonly Action _readCallback; + private readonly Func _allocCallback; + + private readonly UvTcpStreamHandle _tcpStreamHandle; + private readonly GCHandle _selfKeepAlive; + + public UvReadHandle( + UvTcpStreamHandle tcpStreamHandle, + Func allocCallback, + Action readCallback) + { + _uv_read_cb = UvReadCb; + _uv_alloc_cb = UvAllocCb; + + _allocCallback = allocCallback; + _readCallback = readCallback; + _tcpStreamHandle = tcpStreamHandle; + + _tcpStreamHandle.Validate(); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_read_start(_tcpStreamHandle.Handle, _uv_alloc_cb, _uv_read_cb)); + + _selfKeepAlive = GCHandle.Alloc(this, GCHandleType.Normal); + } + + private void UvAllocCb(IntPtr handle, int suggested_size, out UvBuffer buf) + { + buf = _allocCallback(suggested_size); + } + + private void UvReadCb(IntPtr handle, int nread, ref UvBuffer buf) + { + if (nread < 0) + { + var error = Libuv.ExceptionForError(nread); + _readCallback(nread, error); + } + else + { + _readCallback(nread, null); + } + } + + public void Dispose() + { + _tcpStreamHandle.Validate(); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_read_stop(_tcpStreamHandle.Handle)); + + Destroy(); + + GC.SuppressFinalize(this); + } + + ~UvReadHandle() + { + Destroy(); + + // See UvLoopResource's finalizer comment + + Console.WriteLine("TODO: Warning! UvReadHandle was finalized instead of disposed."); + } + + private void Destroy() + { + _selfKeepAlive.Free(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs index ef280549c..bc66a85d0 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvShutdownReq.cs @@ -8,34 +8,31 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking /// /// Summary description for UvShutdownRequest /// - public class UvShutdownReq : UvReq + public class UvShutdownReq : UvMemoryResource { - private readonly static Libuv.uv_shutdown_cb _uv_shutdown_cb = UvShutdownCb; + private readonly uv_shutdown_cb _uv_shutdown_cb; + private readonly Action _callback; - Action _callback; - object _state; - - public void Init(UvLoopHandle loop) + public UvShutdownReq(UvLoopHandle loop, UvTcpStreamHandle stream, Action callback) + : base(loop.ThreadId, GetSize()) { - CreateMemory( - loop.Libuv, - loop.ThreadId, - loop.Libuv.req_size(Libuv.RequestType.SHUTDOWN)); + _uv_shutdown_cb = UvShutdownCb; + + _callback = callback; + Validate(); + stream.Validate(); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_shutdown(this, stream.Handle, _uv_shutdown_cb)); } - public void Shutdown(UvStreamHandle handle, Action callback, object state) + private static int GetSize() { - _callback = callback; - _state = state; - _uv.shutdown(this, handle, _uv_shutdown_cb); + return UnsafeNativeMethods.uv_req_size(RequestType.SHUTDOWN); } - private static void UvShutdownCb(IntPtr ptr, int status) + private void UvShutdownCb(IntPtr ptr, int status) { - var req = FromIntPtr(ptr); - req._callback(req, status, req._state); - req._callback = null; - req._state = null; + _callback(this, status); + Dispose(); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs deleted file mode 100644 index e9f9279f1..000000000 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvStreamHandle.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Diagnostics; -using System.Runtime.InteropServices; - -namespace Microsoft.AspNet.Server.Kestrel.Networking -{ - public abstract class UvStreamHandle : UvHandle - { - private readonly static Libuv.uv_connection_cb _uv_connection_cb = UvConnectionCb; - private readonly static Libuv.uv_alloc_cb _uv_alloc_cb = UvAllocCb; - private readonly static Libuv.uv_read_cb _uv_read_cb = UvReadCb; - - public Action _listenCallback; - public object _listenState; - private GCHandle _listenVitality; - - public Func _allocCallback; - public Action _readCallback; - public object _readState; - private GCHandle _readVitality; - - protected override bool ReleaseHandle() - { - if (_listenVitality.IsAllocated) - { - _listenVitality.Free(); - } - if (_readVitality.IsAllocated) - { - _readVitality.Free(); - } - return base.ReleaseHandle(); - } - - public void Listen(int backlog, Action callback, object state) - { - if (_listenVitality.IsAllocated) - { - throw new InvalidOperationException("TODO: Listen may not be called more than once"); - } - try - { - _listenCallback = callback; - _listenState = state; - _listenVitality = GCHandle.Alloc(this, GCHandleType.Normal); - _uv.listen(this, 10, _uv_connection_cb); - } - catch - { - _listenCallback = null; - _listenState = null; - if (_listenVitality.IsAllocated) - { - _listenVitality.Free(); - } - throw; - } - } - - public void Accept(UvStreamHandle handle) - { - _uv.accept(this, handle); - } - - public void ReadStart( - Func allocCallback, - Action readCallback, - object state) - { - if (_readVitality.IsAllocated) - { - throw new InvalidOperationException("TODO: ReadStop must be called before ReadStart may be called again"); - } - try - { - _allocCallback = allocCallback; - _readCallback = readCallback; - _readState = state; - _readVitality = GCHandle.Alloc(this, GCHandleType.Normal); - _uv.read_start(this, _uv_alloc_cb, _uv_read_cb); - } - catch - { - _allocCallback = null; - _readCallback = null; - _readState = null; - if (_readVitality.IsAllocated) - { - _readVitality.Free(); - } - throw; - } - } - - public void ReadStop() - { - if (!_readVitality.IsAllocated) - { - throw new InvalidOperationException("TODO: ReadStart must be called before ReadStop may be called"); - } - _allocCallback = null; - _readCallback = null; - _readState = null; - _readVitality.Free(); - _uv.read_stop(this); - } - - public int TryWrite(Libuv.uv_buf_t buf) - { - return _uv.try_write(this, new[] { buf }, 1); - } - - - private static void UvConnectionCb(IntPtr handle, int status) - { - var stream = FromIntPtr(handle); - - Exception error; - status = stream.Libuv.Check(status, out error); - - try - { - stream._listenCallback(stream, status, error, stream._listenState); - } - catch (Exception ex) - { - Trace.WriteLine("UvConnectionCb " + ex.ToString()); - } - } - - - private static void UvAllocCb(IntPtr handle, int suggested_size, out Libuv.uv_buf_t buf) - { - var stream = FromIntPtr(handle); - try - { - buf = stream._allocCallback(stream, suggested_size, stream._readState); - } - catch (Exception ex) - { - Trace.WriteLine("UvAllocCb " + ex.ToString()); - buf = stream.Libuv.buf_init(IntPtr.Zero, 0); - throw; - } - } - - private static void UvReadCb(IntPtr handle, int nread, ref Libuv.uv_buf_t buf) - { - var stream = FromIntPtr(handle); - - try - { - if (nread < 0) - { - Exception error; - stream._uv.Check(nread, out error); - stream._readCallback(stream, 0, error, stream._readState); - } - else - { - stream._readCallback(stream, nread, null, stream._readState); - } - } - catch (Exception ex) - { - Trace.WriteLine("UbReadCb " + ex.ToString()); - } - } - - } -} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs index e3f90ad84..dfb7366c0 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpHandle.cs @@ -1,52 +1,19 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.Net; - -namespace Microsoft.AspNet.Server.Kestrel.Networking +namespace Microsoft.AspNet.Server.Kestrel.Networking { - public class UvTcpHandle : UvStreamHandle + public abstract class UvTcpHandle : UvLoopResource { - public void Init(UvLoopHandle loop) + protected UvTcpHandle(UvLoopHandle loop) + :base(loop.ThreadId, GetSize()) { - CreateMemory( - loop.Libuv, - loop.ThreadId, - loop.Libuv.handle_size(Libuv.HandleType.TCP)); + loop.Validate(); + Validate(); - _uv.tcp_init(loop, this); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_tcp_init(loop, Handle)); } - public void Init(UvLoopHandle loop, Action, IntPtr> queueCloseHandle) + private static int GetSize() { - CreateHandle( - loop.Libuv, - loop.ThreadId, - loop.Libuv.handle_size(Libuv.HandleType.TCP), queueCloseHandle); - - _uv.tcp_init(loop, this); - } - - public void Bind(IPEndPoint endpoint) - { - Libuv.sockaddr addr; - var addressText = endpoint.Address.ToString(); - - Exception error1; - _uv.ip4_addr(addressText, endpoint.Port, out addr, out error1); - - if (error1 != null) - { - Exception error2; - _uv.ip6_addr(addressText, endpoint.Port, out addr, out error2); - if (error2 != null) - { - throw error1; - } - } - - _uv.tcp_bind(this, ref addr, 0); + return UnsafeNativeMethods.uv_handle_size(HandleType.TCP); } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpListenHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpListenHandle.cs new file mode 100644 index 000000000..df73a39e4 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpListenHandle.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Net; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public class UvTcpListenHandle : UvTcpHandle + { + private readonly uv_connection_cb _uv_connection_cb; + private readonly GCHandle _selfKeepAlive; + private readonly Action _listenCallback; + + public UvTcpListenHandle( + UvLoopHandle loop, + IPEndPoint endPoint, + int backlog, + Action callback) + : base(loop) + { + _uv_connection_cb = UvConnectionCb; + _selfKeepAlive = GCHandle.Alloc(this, GCHandleType.Normal); + + Bind(endPoint); + _listenCallback = callback; + Validate(); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_listen(Handle, backlog, _uv_connection_cb)); + } + + private void Bind(IPEndPoint endpoint) + { + Validate(); + + Sockaddr addr; + var addressText = endpoint.Address.ToString(); + + var error1 = Libuv.ExceptionForError( + UnsafeNativeMethods.uv_ip4_addr(addressText, endpoint.Port, out addr)); + + if (error1 != null) + { + var error2 = Libuv.ExceptionForError( + UnsafeNativeMethods.uv_ip6_addr(addressText, endpoint.Port, out addr)); + if (error2 != null) + { + throw error1; + } + } + + Libuv.ThrowOnError(UnsafeNativeMethods.uv_tcp_bind(Handle, ref addr, 0)); + } + + private void UvConnectionCb(IntPtr handle, int status) + { + var error = Libuv.ExceptionForError(status); + _listenCallback(status, error); + } + + protected override void Dispose(bool disposing) + { + _selfKeepAlive.Free(); + base.Dispose(disposing); + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpStreamHandle.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpStreamHandle.cs new file mode 100644 index 000000000..4c7db99e5 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvTcpStreamHandle.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public class UvTcpStreamHandle : UvTcpHandle + { + public UvTcpStreamHandle(UvLoopHandle loop, UvTcpListenHandle listenHandle) + : base(loop) + { + listenHandle.Validate(); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_accept(listenHandle.Handle, Handle)); + } + } +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs index dc58e5688..b0fd5a9ca 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteRequest.cs @@ -3,128 +3,84 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace Microsoft.AspNet.Server.Kestrel.Networking { /// /// Summary description for UvWriteRequest /// - public class UvWriteReq : UvReq + public class UvWriteReq : UvMemoryResource { - private readonly static Libuv.uv_write_cb _uv_write_cb = UvWriteCb; + private readonly uv_write_cb _uv_write_cb; - IntPtr _bufs; + private readonly UvTcpStreamHandle _stream; - Action _callback; - object _state; - const int BUFFER_COUNT = 4; + private readonly UvBuffer[] _uvBuffers; + private readonly GCHandle[] _bufferHandles; + private readonly GCHandle _bufferArrayHandle; - List _pins = new List(); + private readonly TaskCompletionSource _tcs; - public void Init(UvLoopHandle loop) + public UvWriteReq( + UvLoopHandle loop, + UvTcpStreamHandle stream, + ArraySegment buffer) + : base(loop.ThreadId, GetSize()) { - var requestSize = loop.Libuv.req_size(Libuv.RequestType.WRITE); - var bufferSize = Marshal.SizeOf(typeof(Libuv.uv_buf_t)) * BUFFER_COUNT; - CreateMemory( - loop.Libuv, - loop.ThreadId, - requestSize + bufferSize); - _bufs = handle + requestSize; + _uv_write_cb = UvWriteCb; + _stream = stream; + + _bufferHandles = new GCHandle[1]; + _uvBuffers = new UvBuffer[1]; + _bufferArrayHandle = GCHandle.Alloc(_uvBuffers, GCHandleType.Pinned); + + _bufferHandles[0] = GCHandle.Alloc(buffer.Array, GCHandleType.Pinned); + _uvBuffers[0] = new UvBuffer( + _bufferHandles[0].AddrOfPinnedObject() + buffer.Offset, + buffer.Count); + + _tcs = new TaskCompletionSource(); } - public unsafe void Write( - UvStreamHandle handle, - ArraySegment> bufs, - Action callback, - object state) + private static int GetSize() { - try - { - // add GCHandle to keeps this SafeHandle alive while request processing - _pins.Add(GCHandle.Alloc(this, GCHandleType.Normal)); - - var pBuffers = (Libuv.uv_buf_t*)_bufs; - var nBuffers = bufs.Count; - if (nBuffers > BUFFER_COUNT) - { - // create and pin buffer array when it's larger than the pre-allocated one - var bufArray = new Libuv.uv_buf_t[nBuffers]; - var gcHandle = GCHandle.Alloc(bufArray, GCHandleType.Pinned); - _pins.Add(gcHandle); - pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject(); - } - - for (var index = 0; index != nBuffers; ++index) - { - // create and pin each segment being written - var buf = bufs.Array[bufs.Offset + index]; - - var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned); - _pins.Add(gcHandle); - pBuffers[index] = Libuv.buf_init( - gcHandle.AddrOfPinnedObject() + buf.Offset, - buf.Count); - } - - _callback = callback; - _state = state; - _uv.write(this, handle, pBuffers, nBuffers, _uv_write_cb); - } - catch - { - _callback = null; - _state = null; - Unpin(this); - throw; - } + return UnsafeNativeMethods.uv_req_size(RequestType.WRITE); } - private static void Unpin(UvWriteReq req) + public Task Task => _tcs.Task; + + public void Write() { - foreach (var pin in req._pins) - { - pin.Free(); - } - req._pins.Clear(); + _stream.Validate(); + Validate(); + Libuv.ThrowOnError(UnsafeNativeMethods.uv_write( + this, + _stream.Handle, + _uvBuffers, + _uvBuffers.Length, + _uv_write_cb)); } - private static void UvWriteCb(IntPtr ptr, int status) + private void UvWriteCb(IntPtr ptr, int status) { - var req = FromIntPtr(ptr); - Unpin(req); - - var callback = req._callback; - req._callback = null; - - var state = req._state; - req._state = null; - - Exception error = null; - if (status < 0) - { - req.Libuv.Check(status, out error); - } - - try - { - callback(req, status, error, state); - } - catch (Exception ex) - { - Trace.WriteLine("UvWriteCb " + ex.ToString()); - } + KestrelTrace.Log.ConnectionWriteCallback(0, status); + + var exception = Libuv.ExceptionForError(status); + if (exception == null) + _tcs.SetResult(0); + else + _tcs.SetException(exception); } - } - public abstract class UvReq : UvMemory - { protected override bool ReleaseHandle() { - DestroyMemory(handle); - handle = IntPtr.Zero; - return true; + foreach (var bufferHandle in _bufferHandles) + bufferHandle.Free(); + _bufferArrayHandle.Free(); + + return base.ReleaseHandle(); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/WindowsNativeBinder.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/WindowsNativeBinder.cs new file mode 100644 index 000000000..f4a68e735 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/WindowsNativeBinder.cs @@ -0,0 +1,45 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Microsoft.AspNet.Server.Kestrel.Networking +{ + public sealed class WindowsNativeBinder : IDisposable + { + private readonly IntPtr handle; + + public WindowsNativeBinder(string dllFile, Type bindTarget) + { + handle = LoadLibrary(dllFile); + if (handle == IntPtr.Zero) + { + throw new DllNotFoundException(dllFile); + } + + foreach (var field in bindTarget.GetTypeInfo().DeclaredFields) + { + var procAddress = GetProcAddress(handle, field.Name); + if (procAddress == IntPtr.Zero) + { + throw new InvalidOperationException("Could not load member: " + field.Name); + } + + var value = Marshal.GetDelegateForFunctionPointer(procAddress, field.FieldType); + field.SetValue(this, value); + } + } + + public void Dispose() + { + FreeLibrary(handle); + GC.SuppressFinalize(this); + } + + [DllImport("kernel32")] + private static extern IntPtr LoadLibrary(string dllToLoad); + [DllImport("kernel32")] + private static extern bool FreeLibrary(IntPtr hModule); + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Server.Kestrel/project.json b/src/Microsoft.AspNet.Server.Kestrel/project.json index 73bb02015..0ea7a3e60 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/project.json +++ b/src/Microsoft.AspNet.Server.Kestrel/project.json @@ -9,6 +9,7 @@ "dnxcore50": { "dependencies": { "System.Collections": "4.0.10-beta-*", + "System.Console" : "4.0.0-beta-*", "System.Diagnostics.Debug": "4.0.10-beta-*", "System.Diagnostics.TraceSource": "4.0.0-beta-*", "System.Diagnostics.Tracing": "4.0.20-beta-*", @@ -25,8 +26,5 @@ "System.Threading.ThreadPool": "4.0.10-beta-*" } } - }, - "compilationOptions": { - "allowUnsafe": true } } diff --git a/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs index f3bc95c1a..6e14fd964 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/EngineTests.cs @@ -15,8 +15,10 @@ namespace Microsoft.AspNet.Server.KestrelTests /// /// Summary description for EngineTests /// - internal class EngineTests + public class EngineTests { + private const int port = 54321; + private async Task App(Frame frame) { for (; ;) @@ -75,51 +77,63 @@ private async Task AppChunked(Frame frame) [Fact] public async Task EngineCanStartAndStop() { - var engine = new KestrelEngine(LibraryManager); - engine.Start(1); - engine.Dispose(); + using (var engine = new KestrelEngine(LibraryManager)) + { + engine.Start(1); + Assert.True(engine.IsClean()); + } } [Fact] public async Task ListenerCanCreateAndDispose() { - var engine = new KestrelEngine(LibraryManager); - engine.Start(1); - var started = engine.CreateServer("http", "localhost", 54321, App); - started.Dispose(); - engine.Dispose(); + using (var engine = new KestrelEngine(LibraryManager)) + { + engine.Start(1); + using (engine.CreateServer("http", "localhost", port, App)) + { + Assert.True(engine.IsClean()); + } + } } - [Fact] public async Task ConnectionCanReadAndWrite() { - var engine = new KestrelEngine(LibraryManager); - engine.Start(1); - var started = engine.CreateServer("http", "localhost", 54321, App); - - Console.WriteLine("Started"); - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socket.Connect(new IPEndPoint(IPAddress.Loopback, 54321)); - socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World")); - socket.Shutdown(SocketShutdown.Send); - var buffer = new byte[8192]; - for (; ;) + using (var engine = new KestrelEngine(LibraryManager)) { - var length = socket.Receive(buffer); - if (length == 0) { break; } - var text = Encoding.ASCII.GetString(buffer, 0, length); + engine.Start(1); + using (engine.CreateServer("http", "localhost", port, App)) + { + using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + Console.WriteLine("Started"); + + socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); + socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World")); + socket.Shutdown(SocketShutdown.Send); + var buffer = new byte[8192]; + var response = string.Empty; + var length = 0; + do + { + length = socket.Receive(buffer); + response += Encoding.ASCII.GetString(buffer, 0, length); + } + while (length > 0); + Assert.False(string.IsNullOrEmpty(response)); + } + Assert.True(engine.IsClean()); + } } - started.Dispose(); - engine.Dispose(); } [Fact] public async Task Http10() { - using (var server = new TestServer(App)) + using (var server = new TestServer(App, port)) { - using (var connection = new TestConnection()) + using (var connection = new TestConnection(port)) { await connection.SendEnd( "POST / HTTP/1.0", @@ -129,6 +143,7 @@ await connection.ReceiveEnd( "HTTP/1.0 200 OK", "", "Hello World"); + Assert.True(server.IsClean()); } } } @@ -136,9 +151,9 @@ await connection.ReceiveEnd( [Fact] public async Task Http11() { - using (var server = new TestServer(AppChunked)) + using (var server = new TestServer(AppChunked, port)) { - using (var connection = new TestConnection()) + using (var connection = new TestConnection(port)) { await connection.SendEnd( "GET / HTTP/1.1", @@ -156,6 +171,7 @@ await connection.ReceiveEnd( "Connection: close", "", "Goodbye"); + Assert.True(server.IsClean()); } } } @@ -164,9 +180,9 @@ await connection.ReceiveEnd( [Fact] public async Task Http10ContentLength() { - using (var server = new TestServer(App)) + using (var server = new TestServer(App, port)) { - using (var connection = new TestConnection()) + using (var connection = new TestConnection(port)) { await connection.Send( "POST / HTTP/1.0", @@ -177,6 +193,7 @@ await connection.ReceiveEnd( "HTTP/1.0 200 OK", "", "Hello World"); + Assert.True(server.IsClean()); } } } @@ -184,9 +201,9 @@ await connection.ReceiveEnd( [Fact] public async Task Http10TransferEncoding() { - using (var server = new TestServer(App)) + using (var server = new TestServer(App, port)) { - using (var connection = new TestConnection()) + using (var connection = new TestConnection(port)) { await connection.Send( "POST / HTTP/1.0", @@ -197,6 +214,7 @@ await connection.ReceiveEnd( "HTTP/1.0 200 OK", "", "Hello World"); + Assert.True(server.IsClean()); } } } @@ -205,9 +223,9 @@ await connection.ReceiveEnd( [Fact] public async Task Http10KeepAlive() { - using (var server = new TestServer(AppChunked)) + using (var server = new TestServer(AppChunked, port)) { - using (var connection = new TestConnection()) + using (var connection = new TestConnection(port)) { await connection.SendEnd( "GET / HTTP/1.0", @@ -226,6 +244,7 @@ await connection.ReceiveEnd( "Content-Length: 7", "", "Goodbye"); + Assert.True(server.IsClean()); } } } @@ -233,9 +252,9 @@ await connection.ReceiveEnd( [Fact] public async Task Http10KeepAliveContentLength() { - using (var server = new TestServer(AppChunked)) + using (var server = new TestServer(AppChunked, port)) { - using (var connection = new TestConnection()) + using (var connection = new TestConnection(port)) { await connection.SendEnd( "POST / HTTP/1.0", @@ -256,6 +275,7 @@ await connection.ReceiveEnd( "Content-Length: 7", "", "Goodbye"); + Assert.True(server.IsClean()); } } } @@ -263,9 +283,9 @@ await connection.ReceiveEnd( [Fact] public async Task Http10KeepAliveTransferEncoding() { - using (var server = new TestServer(AppChunked)) + using (var server = new TestServer(AppChunked, port)) { - using (var connection = new TestConnection()) + using (var connection = new TestConnection(port)) { await connection.SendEnd( "POST / HTTP/1.0", @@ -287,6 +307,7 @@ await connection.ReceiveEnd( "Content-Length: 7", "", "Goodbye"); + Assert.True(server.IsClean()); } } } @@ -294,9 +315,9 @@ await connection.ReceiveEnd( [Fact] public async Task Expect100ContinueForBody() { - using (var server = new TestServer(AppChunked)) + using (var server = new TestServer(AppChunked, port)) { - using (var connection = new TestConnection()) + using (var connection = new TestConnection(port)) { await connection.Send( "POST / HTTP/1.1", @@ -312,31 +333,7 @@ await connection.Receive( "Connection: close", "", "Hello World"); - } - } - } - - - [Fact] - public async Task DisconnectingClient() - { - using (var server = new TestServer(App)) - { - var socket = new Socket(SocketType.Stream, ProtocolType.IP); - socket.Connect(IPAddress.Loopback, 54321); - await Task.Delay(200); - socket.Disconnect(false); - socket.Dispose(); - - await Task.Delay(200); - using (var connection = new TestConnection()) - { - await connection.SendEnd( - "GET / HTTP/1.0", - "\r\n"); - await connection.ReceiveEnd( - "HTTP/1.0 200 OK", - "\r\n"); + Assert.True(server.IsClean()); } } } diff --git a/test/Microsoft.AspNet.Server.KestrelTests/Issues.cs b/test/Microsoft.AspNet.Server.KestrelTests/Issues.cs new file mode 100644 index 000000000..a81688279 --- /dev/null +++ b/test/Microsoft.AspNet.Server.KestrelTests/Issues.cs @@ -0,0 +1,114 @@ +using Microsoft.AspNet.Server.Kestrel.Http; +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNet.Server.KestrelTests +{ + public class Issues + { + private const int port = 54323; + + private async Task App(Frame frame) + { + for (; ;) + { + var buffer = new byte[8192]; + var count = await frame.RequestBody.ReadAsync(buffer, 0, buffer.Length); + if (count == 0) + { + break; + } + await frame.ResponseBody.WriteAsync(buffer, 0, count); + } + } + + [Fact] + public async Task BogusHttpVersionWorks() + { + using (var server = new TestServer(App, port)) + { + using (var connection = new TestConnection(port)) + { + await connection.SendEnd( + "POST / bogus", + // The connection is handled as HTTP/1.1 so keep-alive is active + "Connection: close", + "", + "Hello World"); + await connection.ReceiveEnd( + "bogus 200 OK", + "", + "Hello World"); + Assert.True(false, "This should not be reached"); + } + } + } + + [Fact] + public async Task EndingBeforeStartingTheRequestDoesNotEndConnection() + { + using (var server = new TestServer(App, port)) + { + using (var socket = new Socket(SocketType.Stream, ProtocolType.IP)) + { + socket.Connect(IPAddress.Loopback, port); + socket.Disconnect(false); + } + await Task.Delay(200); + Assert.True(server.IsClean(), "Is not clean"); + } + } + + [Fact] + public async Task EndingInRequestLineDoesNotEndConnection() + { + using (var server = new TestServer(App, port)) + { + using (var connection = new TestConnection(port)) + { + await connection.SendEnd("POST / Ht"); + await Task.Delay(200); + Assert.True(server.IsClean(), "Is not clean"); + } + } + } + + [Fact] + public async Task EndingInHeadersDoesNotEndConnection() + { + using (var server = new TestServer(App, port)) + { + using (var connection = new TestConnection(port)) + { + await connection.SendEnd( + "POST / Http/1.0", + "Conn" + ); + await Task.Delay(200); + Assert.True(server.IsClean(), "Is not clean"); + } + } + } + + [Fact] + public async Task EndingInContentDoesNotEndConnection() + { + using (var server = new TestServer(App, port)) + { + using (var connection = new TestConnection(port)) + { + await connection.SendEnd( + "POST / HTTP/1.0", + "Content-Length: 100", + "", + "a"); + await Task.Delay(200); + Assert.True(server.IsClean(), "Is not clean"); + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Server.KestrelTests/NetworkingTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/NetworkingTests.cs index cca8bb0f4..94468489f 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/NetworkingTests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/NetworkingTests.cs @@ -19,11 +19,11 @@ namespace Microsoft.AspNet.Server.KestrelTests /// public class NetworkingTests { - Libuv _uv; + private const int port = 54322; + public NetworkingTests() { - var engine = new KestrelEngine(LibraryManager); - _uv = engine.Libuv; + new KestrelEngine(LibraryManager); } ILibraryManager LibraryManager @@ -47,76 +47,78 @@ ILibraryManager LibraryManager [Fact] public async Task LoopCanBeInitAndClose() { - var loop = new UvLoopHandle(); - loop.Init(_uv); - loop.Run(); - loop.Dispose(); + using (var loop = new UvLoopHandle()) + loop.Run(); } [Fact] public async Task AsyncCanBeSent() { - var loop = new UvLoopHandle(); - loop.Init(_uv); - var trigger = new UvAsyncHandle(); var called = false; - trigger.Init(loop, () => + UvAsyncHandle trigger = null; + using (var loop = new UvLoopHandle()) { - called = true; - trigger.Dispose(); - }); - trigger.Send(); - loop.Run(); - loop.Dispose(); + trigger = new UvAsyncHandle(loop, () => + { + called = true; + trigger.Dispose(); + }); + trigger.Send(); + loop.Run(); + } Assert.True(called); } [Fact] public async Task SocketCanBeInitAndClose() { - var loop = new UvLoopHandle(); - loop.Init(_uv); - var tcp = new UvTcpHandle(); - tcp.Init(loop); - tcp.Bind(new IPEndPoint(IPAddress.Loopback, 0)); - tcp.Dispose(); - loop.Run(); - loop.Dispose(); + using (var loop = new UvLoopHandle()) + { + var tcp = new UvTcpListenHandle( + loop, + new IPEndPoint(IPAddress.Loopback, 0), + 10, + null + ); + tcp.Dispose(); + loop.Run(); + } } [Fact] public async Task SocketCanListenAndAccept() { - var loop = new UvLoopHandle(); - loop.Init(_uv); - var tcp = new UvTcpHandle(); - tcp.Init(loop); - tcp.Bind(new IPEndPoint(IPAddress.Loopback, 54321)); - tcp.Listen(10, (stream, status, error, state) => + Task t; + using (var loop = new UvLoopHandle()) { - var tcp2 = new UvTcpHandle(); - tcp2.Init(loop); - stream.Accept(tcp2); - tcp2.Dispose(); - stream.Dispose(); - }, null); - var t = Task.Run(async () => - { - var socket = new Socket( - AddressFamily.InterNetwork, - SocketType.Stream, - ProtocolType.Tcp); - await Task.Factory.FromAsync( - socket.BeginConnect, - socket.EndConnect, - new IPEndPoint(IPAddress.Loopback, 54321), - null, - TaskCreationOptions.None); - socket.Dispose(); - }); - loop.Run(); - loop.Dispose(); + UvTcpListenHandle tcp = null; + tcp = new UvTcpListenHandle( + loop, + new IPEndPoint(IPAddress.Loopback, port), + 10, + (status, error) => + { + var tcp2 = new UvTcpStreamHandle(loop, tcp); + tcp2.Dispose(); + tcp.Dispose(); + }); + t = Task.Run(async () => + { + var socket = new Socket( + AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + await Task.Factory.FromAsync( + socket.BeginConnect, + socket.EndConnect, + new IPEndPoint(IPAddress.Loopback, port), + null, + TaskCreationOptions.None); + socket.Dispose(); + }); + loop.Run(); + } await t; } @@ -125,142 +127,143 @@ await Task.Factory.FromAsync( public async Task SocketCanRead() { int bytesRead = 0; - var loop = new UvLoopHandle(); - loop.Init(_uv); - var tcp = new UvTcpHandle(); - tcp.Init(loop); - tcp.Bind(new IPEndPoint(IPAddress.Loopback, 54321)); - tcp.Listen(10, (_, status, error, state) => + Task t; + using (var loop = new UvLoopHandle()) { - Console.WriteLine("Connected"); - var tcp2 = new UvTcpHandle(); - tcp2.Init(loop); - tcp.Accept(tcp2); - var data = Marshal.AllocCoTaskMem(500); - tcp2.ReadStart( - (a, b, c) => _uv.buf_init(data, 500), - (__, nread, error2, state2) => + UvTcpListenHandle tcp = null; + tcp = new UvTcpListenHandle( + loop, + new IPEndPoint(IPAddress.Loopback, port), + 10, + (status, error) => { - bytesRead += nread; - if (nread == 0) - { - tcp2.Dispose(); - } - }, - null); - tcp.Dispose(); - }, null); - Console.WriteLine("Task.Run"); - var t = Task.Run(async () => - { - var socket = new Socket( - AddressFamily.InterNetwork, - SocketType.Stream, - ProtocolType.Tcp); - await Task.Factory.FromAsync( - socket.BeginConnect, - socket.EndConnect, - new IPEndPoint(IPAddress.Loopback, 54321), - null, - TaskCreationOptions.None); - await Task.Factory.FromAsync( - socket.BeginSend, - socket.EndSend, - new[] { new ArraySegment(new byte[] { 1, 2, 3, 4, 5 }) }, - SocketFlags.None, - null, - TaskCreationOptions.None); - socket.Dispose(); - }); - loop.Run(); - loop.Dispose(); + Console.WriteLine("Connected"); + var tcp2 = new UvTcpStreamHandle(loop, tcp); + var data = Marshal.AllocCoTaskMem(500); + UvReadHandle read = null; + read = new UvReadHandle(tcp2, + (b) => new UvBuffer(data, 500), + (nread, error2) => + { + bytesRead += nread; + if (nread <= 0) + { + read.Dispose(); + tcp2.Dispose(); + } + }); + tcp.Dispose(); + }); + Console.WriteLine("Task.Run"); + t = Task.Run(async () => + { + var socket = new Socket( + AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + await Task.Factory.FromAsync( + socket.BeginConnect, + socket.EndConnect, + new IPEndPoint(IPAddress.Loopback, port), + null, + TaskCreationOptions.None); + await Task.Factory.FromAsync( + socket.BeginSend, + socket.EndSend, + new[] { new ArraySegment(new byte[] { 1, 2, 3, 4, 5 }) }, + SocketFlags.None, + null, + TaskCreationOptions.None); + socket.Dispose(); + }); + loop.Run(); + } await t; } [Fact] public async Task SocketCanReadAndWrite() { + Task t; int bytesRead = 0; - var loop = new UvLoopHandle(); - loop.Init(_uv); - var tcp = new UvTcpHandle(); - tcp.Init(loop); - tcp.Bind(new IPEndPoint(IPAddress.Loopback, 54321)); - tcp.Listen(10, (_, status, error, state) => + using (var loop = new UvLoopHandle()) { - Console.WriteLine("Connected"); - var tcp2 = new UvTcpHandle(); - tcp2.Init(loop); - tcp.Accept(tcp2); - var data = Marshal.AllocCoTaskMem(500); - tcp2.ReadStart( - (a, b, c) => tcp2.Libuv.buf_init(data, 500), - (__, nread, error2, state2) => + UvTcpListenHandle tcp = null; + tcp = new UvTcpListenHandle( + loop, + new IPEndPoint(IPAddress.Loopback, port), + 10, + (status, error) => { - bytesRead += nread; - if (nread == 0) - { - tcp2.Dispose(); - } - else - { - for (var x = 0; x != 2; ++x) + Console.WriteLine("Connected"); + var tcp2 = new UvTcpStreamHandle(loop, tcp); + var data = Marshal.AllocCoTaskMem(500); + UvReadHandle read = null; + read = new UvReadHandle(tcp2, + (b) => new UvBuffer(data, 500), + (nread, error2) => { - var req = new UvWriteReq(); - req.Init(loop); - req.Write( - tcp2, - new ArraySegment>( - new[] { new ArraySegment(new byte[] { 65, 66, 67, 68, 69 }) } - ), - (_1, _2, _3, _4) => { }, - null); - } - } - }, - null); - tcp.Dispose(); - }, null); - Console.WriteLine("Task.Run"); - var t = Task.Run(async () => - { - var socket = new Socket( - AddressFamily.InterNetwork, - SocketType.Stream, - ProtocolType.Tcp); - await Task.Factory.FromAsync( - socket.BeginConnect, - socket.EndConnect, - new IPEndPoint(IPAddress.Loopback, 54321), - null, - TaskCreationOptions.None); - await Task.Factory.FromAsync( - socket.BeginSend, - socket.EndSend, - new[] { new ArraySegment(new byte[] { 1, 2, 3, 4, 5 }) }, - SocketFlags.None, - null, - TaskCreationOptions.None); - socket.Shutdown(SocketShutdown.Send); - var buffer = new ArraySegment(new byte[2048]); - for (; ;) + bytesRead += nread; + if (nread <= 0) + { + read.Dispose(); + tcp2.Dispose(); + } + else + { + for (var x = 0; x != 2; ++x) + { + var req = new UvWriteReq( + loop, + tcp2, + new ArraySegment( + new byte[] { 65, 66, 67, 68, 69 }) + ); + } + } + }); + tcp.Dispose(); + }); + Console.WriteLine("Task.Run"); + t = Task.Run(async () => { - var count = await Task.Factory.FromAsync( - socket.BeginReceive, - socket.EndReceive, - new[] { buffer }, + var socket = new Socket( + AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp); + await Task.Factory.FromAsync( + socket.BeginConnect, + socket.EndConnect, + new IPEndPoint(IPAddress.Loopback, port), + null, + TaskCreationOptions.None); + await Task.Factory.FromAsync( + socket.BeginSend, + socket.EndSend, + new[] { new ArraySegment(new byte[] { 1, 2, 3, 4, 5 }) }, SocketFlags.None, null, TaskCreationOptions.None); - Console.WriteLine("count {0} {1}", - count, - System.Text.Encoding.ASCII.GetString(buffer.Array, 0, count)); - if (count <= 0) break; - } - socket.Dispose(); - }); - loop.Run(); - loop.Dispose(); + socket.Shutdown(SocketShutdown.Send); + var buffer = new ArraySegment(new byte[2048]); + for (; ;) + { + var count = await Task.Factory.FromAsync( + socket.BeginReceive, + socket.EndReceive, + new[] { buffer }, + SocketFlags.None, + null, + TaskCreationOptions.None); + Console.WriteLine("count {0} {1}", + count, + System.Text.Encoding.ASCII.GetString(buffer.Array, 0, count)); + if (count <= 0) break; + } + socket.Dispose(); + }); + loop.Run(); + } await t; } } diff --git a/test/Microsoft.AspNet.Server.KestrelTests/Program.cs b/test/Microsoft.AspNet.Server.KestrelTests/Program.cs index 9e36b9f8c..3029ca059 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/Program.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/Program.cs @@ -9,7 +9,7 @@ public class Program { public void Main() { - new EngineTests().DisconnectingClient().Wait(); + new EngineTests().Http10().Wait(); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNet.Server.KestrelTests/TestConnection.cs b/test/Microsoft.AspNet.Server.KestrelTests/TestConnection.cs index be079ad17..485082867 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/TestConnection.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/TestConnection.cs @@ -18,15 +18,15 @@ public class TestConnection : IDisposable private NetworkStream _stream; private StreamReader _reader; - public TestConnection() + public TestConnection(int port) { - Create(); + Create(port); } - public void Create() + public void Create(int port) { _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - _socket.Connect(new IPEndPoint(IPAddress.Loopback, 54321)); + _socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); _stream = new NetworkStream(_socket, false); _reader = new StreamReader(_stream, Encoding.ASCII); diff --git a/test/Microsoft.AspNet.Server.KestrelTests/TestInput.cs b/test/Microsoft.AspNet.Server.KestrelTests/TestInput.cs index a9448ac94..dbc375e8e 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/TestInput.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/TestInput.cs @@ -1,5 +1,6 @@ using System; using Microsoft.AspNet.Server.Kestrel.Http; +using System.Threading.Tasks; namespace Microsoft.AspNet.Server.KestrelTests { @@ -32,8 +33,9 @@ public void Add(string text, bool fin = false) } } - public void ProduceContinue() + public Task ProduceContinueAsync() { + return Task.FromResult(0); } public void Pause() @@ -44,12 +46,16 @@ public void Resume() { } - public void Write(ArraySegment data, Action callback, object state) + public Task WriteAsync(ArraySegment data) { + return Task.FromResult(0); } - public void End(ProduceEndType endType) + public Task EndAsync(ProduceEndType endType) { + return Task.FromResult(0); } + + public bool IsInKeepAlive => false; } } diff --git a/test/Microsoft.AspNet.Server.KestrelTests/TestServer.cs b/test/Microsoft.AspNet.Server.KestrelTests/TestServer.cs index 0e2c9f3d3..a3602f8f6 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/TestServer.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/TestServer.cs @@ -15,9 +15,9 @@ public class TestServer : IDisposable private KestrelEngine _engine; private IDisposable _server; - public TestServer(Func app) + public TestServer(Func app, int port) { - Create(app); + Create(app, port); } ILibraryManager LibraryManager @@ -40,17 +40,19 @@ ILibraryManager LibraryManager } } - public void Create(Func app) + public void Create(Func app, int port) { _engine = new KestrelEngine(LibraryManager); _engine.Start(1); _server = _engine.CreateServer( "http", "localhost", - 54321, + port, app); } + public bool IsClean() => _engine.IsClean(); + public void Dispose() { _server.Dispose();