From 58d8c851c5fdeea8189529b4d23cff9869489580 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 19 Nov 2015 21:50:10 +0000 Subject: [PATCH 1/8] Remove CreateLinkedTokenSource Resolves #407 --- src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index 7bb0faa0e..767ab0425 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -43,8 +43,7 @@ public partial class Frame : FrameContext, IFrameControl private Task _requestProcessingTask; private volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx private volatile bool _requestAborted; - private CancellationTokenSource _disconnectCts = new CancellationTokenSource(); - private CancellationTokenSource _requestAbortCts; + private CancellationTokenSource _disconnectOrAbortedCts = new CancellationTokenSource(); private FrameRequestStream _requestBody; private FrameResponseStream _responseBody; @@ -145,8 +144,6 @@ public void Reset() } _prepareRequest?.Invoke(this); - - _requestAbortCts?.Dispose(); } public void ResetResponseHeaders() @@ -199,7 +196,7 @@ public void Abort() ConnectionControl.End(ProduceEndType.SocketDisconnect); SocketInput.AbortAwaiting(); - _disconnectCts.Cancel(); + _disconnectOrAbortedCts.Cancel(); } catch (Exception ex) { @@ -248,8 +245,7 @@ public async Task RequestProcessingAsync() ResponseBody = _responseBody; DuplexStream = new FrameDuplexStream(RequestBody, ResponseBody); - _requestAbortCts = CancellationTokenSource.CreateLinkedTokenSource(_disconnectCts.Token); - RequestAborted = _requestAbortCts.Token; + RequestAborted = _disconnectOrAbortedCts.Token; var httpContext = HttpContextFactory.Create(this); try @@ -302,7 +298,7 @@ public async Task RequestProcessingAsync() { try { - _disconnectCts.Dispose(); + _disconnectOrAbortedCts.Dispose(); // If _requestAborted is set, the connection has already been closed. if (!_requestAborted) From 3c7431aa354e8092571903cc3b96b0a069324a80 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 19 Nov 2015 23:25:31 +0000 Subject: [PATCH 2/8] Change disconnect to abort cts --- .../Http/Frame.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index 767ab0425..815670619 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -43,7 +43,7 @@ public partial class Frame : FrameContext, IFrameControl private Task _requestProcessingTask; private volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx private volatile bool _requestAborted; - private CancellationTokenSource _disconnectOrAbortedCts = new CancellationTokenSource(); + private CancellationTokenSource _abortedCts; private FrameRequestStream _requestBody; private FrameResponseStream _responseBody; @@ -144,6 +144,9 @@ public void Reset() } _prepareRequest?.Invoke(this); + + _abortedCts?.Dispose(); + _abortedCts = null; } public void ResetResponseHeaders() @@ -196,12 +199,17 @@ public void Abort() ConnectionControl.End(ProduceEndType.SocketDisconnect); SocketInput.AbortAwaiting(); - _disconnectOrAbortedCts.Cancel(); + _abortedCts?.Cancel(); } catch (Exception ex) { Log.LogError("Abort", ex); } + finally + { + _abortedCts?.Dispose(); + _abortedCts = null; + } } /// @@ -245,7 +253,8 @@ public async Task RequestProcessingAsync() ResponseBody = _responseBody; DuplexStream = new FrameDuplexStream(RequestBody, ResponseBody); - RequestAborted = _disconnectOrAbortedCts.Token; + _abortedCts = new CancellationTokenSource(); + RequestAborted = _abortedCts.Token; var httpContext = HttpContextFactory.Create(this); try @@ -298,7 +307,8 @@ public async Task RequestProcessingAsync() { try { - _disconnectOrAbortedCts.Dispose(); + _abortedCts?.Dispose(); + _abortedCts = null; // If _requestAborted is set, the connection has already been closed. if (!_requestAborted) From 174ec739bbe770b41b265f947abf29315f7a5921 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Fri, 20 Nov 2015 00:30:08 +0000 Subject: [PATCH 3/8] Don't log ODEs thrown from _abortedCts.Cancel --- src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index 815670619..3922f4d9d 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -199,7 +199,15 @@ public void Abort() ConnectionControl.End(ProduceEndType.SocketDisconnect); SocketInput.AbortAwaiting(); - _abortedCts?.Cancel(); + try + { + _abortedCts?.Cancel(); + } + catch (ObjectDisposedException) + { + // Don't log ODEs thrown from _abortedCts.Cancel() + // If _abortedCts is disposed, the app has already completed. + } } catch (Exception ex) { From c7d7f0e57514b990e1dc04dd794912c2c79965dd Mon Sep 17 00:00:00 2001 From: stephentoub Date: Sat, 21 Nov 2015 08:38:48 -0500 Subject: [PATCH 4/8] Lazily allocate the RequestAborted CTS Avoid allocating the CancellationTokenSource unless it's actually requested. This makes it pay-for-play with regards to code that actually asks for the RequestAborted token and requests that are aborted. --- .../Http/Frame.cs | 61 ++++++++++++++----- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index 3922f4d9d..4d0b19af4 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -44,6 +44,7 @@ public partial class Frame : FrameContext, IFrameControl private volatile bool _requestProcessingStopping; // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx private volatile bool _requestAborted; private CancellationTokenSource _abortedCts; + private CancellationToken? _manuallySetRequestAbortToken; private FrameRequestStream _requestBody; private FrameResponseStream _responseBody; @@ -92,8 +93,47 @@ public Frame(ConnectionContext context, public Stream DuplexStream { get; set; } - public CancellationToken RequestAborted { get; set; } + public CancellationToken RequestAborted + { + get + { + // If a request abort token was previously explicitly set, return it. + if (_manuallySetRequestAbortToken.HasValue) + return _manuallySetRequestAbortToken.Value; + + // Otherwise, get the abort CTS. If we have one, which would mean that someone previously + // asked for the RequestAborted token, simply return its token. If we don't, + // check to see whether we've already aborted, in which case just return an + // already canceled token. Finally, force a source into existence if we still + // don't have one, and return its token. + var cts = _abortedCts; + return + cts != null ? cts.Token : + _requestAborted ? new CancellationToken(true) : + RequestAbortedSource.Token; + } + set + { + // Set an abort token, overriding one we create internally. This setter and associated + // field exist purely to support IHttpRequestLifetimeFeature.set_RequestAborted. + _manuallySetRequestAbortToken = value; + } + } + private CancellationTokenSource RequestAbortedSource + { + get + { + // Get the abort token, lazily-initializing it if necessary. + // Make sure it's canceled if an abort request already came in. + var cts = LazyInitializer.EnsureInitialized(ref _abortedCts, () => new CancellationTokenSource()); + if (_requestAborted) + { + cts.Cancel(); + } + return cts; + } + } public bool HasResponseStarted { get { return _responseStarted; } @@ -145,7 +185,7 @@ public void Reset() _prepareRequest?.Invoke(this); - _abortedCts?.Dispose(); + _manuallySetRequestAbortToken = null; _abortedCts = null; } @@ -198,16 +238,7 @@ public void Abort() { ConnectionControl.End(ProduceEndType.SocketDisconnect); SocketInput.AbortAwaiting(); - - try - { - _abortedCts?.Cancel(); - } - catch (ObjectDisposedException) - { - // Don't log ODEs thrown from _abortedCts.Cancel() - // If _abortedCts is disposed, the app has already completed. - } + RequestAbortedSource.Cancel(); } catch (Exception ex) { @@ -215,7 +246,6 @@ public void Abort() } finally { - _abortedCts?.Dispose(); _abortedCts = null; } } @@ -261,8 +291,8 @@ public async Task RequestProcessingAsync() ResponseBody = _responseBody; DuplexStream = new FrameDuplexStream(RequestBody, ResponseBody); - _abortedCts = new CancellationTokenSource(); - RequestAborted = _abortedCts.Token; + _abortedCts = null; + _manuallySetRequestAbortToken = null; var httpContext = HttpContextFactory.Create(this); try @@ -315,7 +345,6 @@ public async Task RequestProcessingAsync() { try { - _abortedCts?.Dispose(); _abortedCts = null; // If _requestAborted is set, the connection has already been closed. From a5ff8a1eccbc0ed1a0ede84a3e0dd519de7043a5 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 24 Nov 2015 03:20:38 +0000 Subject: [PATCH 5/8] Resuse writes, initalize queues --- .../Http/SocketOutput.cs | 115 ++++++++++++++---- .../Infrastructure/KestrelThread.cs | 8 +- .../Networking/UvWriteReq.cs | 4 +- 3 files changed, 100 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs index 911a5a967..e1246ae67 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/SocketOutput.cs @@ -17,6 +17,7 @@ public class SocketOutput : ISocketOutput private const int _maxPendingWrites = 3; private const int _maxBytesPreCompleted = 65536; private const int _initialTaskQueues = 64; + private const int _maxPooledWriteContexts = 32; private static WaitCallback _returnBlocks = (state) => ReturnBlocks((MemoryPoolBlock2)state); @@ -38,6 +39,7 @@ public class SocketOutput : ISocketOutput // This locks access to to all of the below fields private readonly object _contextLock = new object(); + private bool _isDisposed = false; // The number of write operations that have been scheduled so far // but have not completed. @@ -48,6 +50,7 @@ public class SocketOutput : ISocketOutput private WriteContext _nextWriteContext; private readonly Queue> _tasksPending; private readonly Queue> _tasksCompleted; + private readonly Queue _writeContextPool; public SocketOutput( KestrelThread thread, @@ -64,6 +67,7 @@ public SocketOutput( _log = log; _tasksPending = new Queue>(_initialTaskQueues); _tasksCompleted = new Queue>(_initialTaskQueues); + _writeContextPool = new Queue(_maxPooledWriteContexts); _head = memory.Lease(); _tail = _head; @@ -90,7 +94,14 @@ public Task WriteAsync( { if (_nextWriteContext == null) { - _nextWriteContext = new WriteContext(this); + if (_writeContextPool.Count > 0) + { + _nextWriteContext = _writeContextPool.Dequeue(); + } + else + { + _nextWriteContext = new WriteContext(this); + } } if (socketShutdownSend) @@ -269,9 +280,12 @@ private void WriteAllPending() } // This is called on the libuv event loop - private void OnWriteCompleted(int bytesWritten, int status, Exception error) + private void OnWriteCompleted(WriteContext writeContext) { - _log.ConnectionWriteCallback(_connectionId, status); + var bytesWritten = writeContext.ByteCount; + var status = writeContext.WriteStatus; + var error = writeContext.WriteError; + if (error != null) { @@ -285,6 +299,7 @@ private void OnWriteCompleted(int bytesWritten, int status, Exception error) lock (_contextLock) { + PoolWriteContext(writeContext); if (_nextWriteContext != null) { scheduleWrite = true; @@ -332,10 +347,10 @@ private void OnWriteCompleted(int bytesWritten, int status, Exception error) } } + _log.ConnectionWriteCallback(_connectionId, status); + if (scheduleWrite) { - // ScheduleWrite(); - // on right thread, fairness issues? WriteAllPending(); } @@ -370,6 +385,32 @@ private void ReturnAllBlocks() } } + private void PoolWriteContext(WriteContext writeContext) + { + // called inside _contextLock + if (!_isDisposed && _writeContextPool.Count < _maxPooledWriteContexts) + { + writeContext.Reset(); + _writeContextPool.Enqueue(writeContext); + } + else + { + writeContext.Dispose(); + } + } + + public void Dispose() + { + lock (_contextLock) + { + _isDisposed = true; + while (_writeContextPool.Count > 0) + { + _writeContextPool.Dequeue().Dispose(); + } + } + } + void ISocketOutput.Write(ArraySegment buffer, bool immediate) { var task = WriteAsync(buffer, immediate); @@ -389,14 +430,14 @@ Task ISocketOutput.WriteAsync(ArraySegment buffer, bool immediate, Cancell return WriteAsync(buffer, immediate); } - private class WriteContext + private class WriteContext : IDisposable { private static WaitCallback _returnWrittenBlocks = (state) => ReturnWrittenBlocks((MemoryPoolBlock2)state); private MemoryPoolIterator2 _lockedStart; private MemoryPoolIterator2 _lockedEnd; private int _bufferCount; - private int _byteCount; + public int ByteCount; public SocketOutput Self; @@ -406,11 +447,15 @@ private class WriteContext public int WriteStatus; public Exception WriteError; + private UvWriteReq _writeReq; + public int ShutdownSendStatus; public WriteContext(SocketOutput self) { Self = self; + _writeReq = new UvWriteReq(Self._log); + _writeReq.Init(Self._thread.Loop); } /// @@ -420,18 +465,14 @@ public void DoWriteIfNeeded() { LockWrite(); - if (_byteCount == 0 || Self._socket.IsClosed) + if (ByteCount == 0 || Self._socket.IsClosed) { DoShutdownIfNeeded(); return; } - var writeReq = new UvWriteReq(Self._log); - writeReq.Init(Self._thread.Loop); - - writeReq.Write(Self._socket, _lockedStart, _lockedEnd, _bufferCount, (_writeReq, status, error, state) => + _writeReq.Write(Self._socket, _lockedStart, _lockedEnd, _bufferCount, (_writeReq, status, error, state) => { - _writeReq.Dispose(); var _this = (WriteContext)state; _this.ScheduleReturnFullyWrittenBlocks(); _this.WriteStatus = status; @@ -440,7 +481,11 @@ public void DoWriteIfNeeded() }, this); Self._head = _lockedEnd.Block; - Self._head.Start = _lockedEnd.Index; + if (Self._head != null) + { + // Avoid shutdown race + Self._head.Start = _lockedEnd.Index; + } } /// @@ -462,7 +507,7 @@ public void DoShutdownIfNeeded() var _this = (WriteContext)state; _this.ShutdownSendStatus = status; - _this.Self._log.ConnectionWroteFin(Self._connectionId, status); + _this.Self._log.ConnectionWroteFin(_this.Self._connectionId, status); _this.DoDisconnectIfNeeded(); }, this); @@ -473,21 +518,28 @@ public void DoShutdownIfNeeded() /// public void DoDisconnectIfNeeded() { - if (SocketDisconnect == false || Self._socket.IsClosed) + if (SocketDisconnect == false) { Complete(); return; } + else if (Self._socket.IsClosed) + { + Self.Dispose(); + Complete(); + return; + } Self._socket.Dispose(); Self.ReturnAllBlocks(); + Self.Dispose(); Self._log.ConnectionStop(Self._connectionId); Complete(); } public void Complete() { - Self.OnWriteCompleted(_byteCount, WriteStatus, WriteError); + Self.OnWriteCompleted(this); } private void ScheduleReturnFullyWrittenBlocks() @@ -539,23 +591,44 @@ private void LockWrite() if (_lockedStart.Block == _lockedEnd.Block) { - _byteCount = _lockedEnd.Index - _lockedStart.Index; + ByteCount = _lockedEnd.Index - _lockedStart.Index; _bufferCount = 1; return; } - _byteCount = _lockedStart.Block.Data.Offset + _lockedStart.Block.Data.Count - _lockedStart.Index; + ByteCount = _lockedStart.Block.Data.Offset + _lockedStart.Block.Data.Count - _lockedStart.Index; _bufferCount = 1; for (var block = _lockedStart.Block.Next; block != _lockedEnd.Block; block = block.Next) { - _byteCount += block.Data.Count; + ByteCount += block.Data.Count; _bufferCount++; } - _byteCount += _lockedEnd.Index - _lockedEnd.Block.Data.Offset; + ByteCount += _lockedEnd.Index - _lockedEnd.Block.Data.Offset; _bufferCount++; } + + public void Reset() + { + _lockedStart = default(MemoryPoolIterator2); + _lockedEnd = default(MemoryPoolIterator2); + _bufferCount = 0; + ByteCount = 0; + + SocketShutdownSend = false; + SocketDisconnect = false; + + WriteStatus = 0; + WriteError = null; + + ShutdownSendStatus = 0; + } + + public void Dispose() + { + _writeReq.Dispose(); + } } } } diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs index a8608de70..cbb35ae18 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/KestrelThread.cs @@ -24,10 +24,10 @@ public class KestrelThread private Thread _thread; private UvLoopHandle _loop; private UvAsyncHandle _post; - private Queue _workAdding = new Queue(); - private Queue _workRunning = new Queue(); - private Queue _closeHandleAdding = new Queue(); - private Queue _closeHandleRunning = new Queue(); + private Queue _workAdding = new Queue(1024); + private Queue _workRunning = new Queue(1024); + private Queue _closeHandleAdding = new Queue(256); + private Queue _closeHandleRunning = new Queue(256); private object _workSync = new Object(); private bool _stopImmediate = false; private bool _initCompleted = false; diff --git a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteReq.cs b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteReq.cs index 2695036f3..21a1aaf77 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteReq.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Networking/UvWriteReq.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking /// public class UvWriteReq : UvRequest { - private readonly static Libuv.uv_write_cb _uv_write_cb = UvWriteCb; + private readonly static Libuv.uv_write_cb _uv_write_cb = (IntPtr ptr, int status) => UvWriteCb(ptr, status); private IntPtr _bufs; @@ -22,7 +22,7 @@ public class UvWriteReq : UvRequest private object _state; private const int BUFFER_COUNT = 4; - private List _pins = new List(); + private List _pins = new List(BUFFER_COUNT + 1); public UvWriteReq(IKestrelTrace logger) : base(logger) { From f34420a540b9d31816dd534c013336e62f366e07 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 24 Nov 2015 05:12:19 +0000 Subject: [PATCH 6/8] Improved RequestProcessingAsync legibility With precomputed tasks Hopefully #391, the good bits... --- .../Http/Frame.cs | 84 ++++++++++++++----- .../Infrastructure/TaskUtilities.cs | 2 + 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index e445bc815..054784c42 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -263,28 +263,15 @@ public async Task RequestProcessingAsync() { try { - var terminated = false; - while (!terminated && !_requestProcessingStopping) + while (!_requestProcessingStopping) { - while (!terminated && !_requestProcessingStopping && !TakeStartLine(SocketInput)) + if (!await ReadStartLineAsync() || + !await ReadHeadersAsync()) { - terminated = SocketInput.RemoteIntakeFin; - if (!terminated) - { - await SocketInput; - } - } - - while (!terminated && !_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) - { - terminated = SocketInput.RemoteIntakeFin; - if (!terminated) - { - await SocketInput; - } + break; } - if (!terminated && !_requestProcessingStopping) + if (!_requestProcessingStopping) { var messageBody = MessageBody.For(HttpVersion, _requestHeaders, this); _keepAlive = messageBody.RequestKeepAlive; @@ -337,7 +324,10 @@ public async Task RequestProcessingAsync() _responseBody.StopAcceptingWrites(); } - terminated = !_keepAlive; + if (!_keepAlive) + { + break; + } } Reset(); @@ -372,6 +362,62 @@ public async Task RequestProcessingAsync() } } } + private Task ReadStartLineAsync() + { + if (!_requestProcessingStopping && !TakeStartLine(SocketInput)) + { + if (SocketInput.RemoteIntakeFin) + { + return TaskUtilities.CompletedFalseTask; + }; + return ReadStartLineAwaitAsync(); + } + return TaskUtilities.CompletedTrueTask; + } + + private async Task ReadStartLineAwaitAsync() + { + await SocketInput; + + while (!_requestProcessingStopping && !TakeStartLine(SocketInput)) + { + if (SocketInput.RemoteIntakeFin) + { + return false; + }; + + await SocketInput; + } + return true; + } + + private Task ReadHeadersAsync() + { + if (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) + { + if (SocketInput.RemoteIntakeFin) + { + return TaskUtilities.CompletedFalseTask; + }; + return ReadHeadersAwaitAsync(); + } + return TaskUtilities.CompletedTrueTask; + } + + private async Task ReadHeadersAwaitAsync() + { + await SocketInput; + + while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) + { + if (SocketInput.RemoteIntakeFin) + { + return false; + }; + await SocketInput; + } + return true; + } public void OnStarting(Func callback, object state) { diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/TaskUtilities.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/TaskUtilities.cs index e67ded5ac..426ab7a9e 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/TaskUtilities.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/TaskUtilities.cs @@ -12,5 +12,7 @@ public static class TaskUtilities #else public static Task CompletedTask = Task.FromResult(null); #endif + public static Task CompletedTrueTask = Task.FromResult(true); + public static Task CompletedFalseTask = Task.FromResult(false); } } \ No newline at end of file From f3f11a5c6dd8a0258a4d29a680e3031266311c59 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 19 Nov 2015 21:47:04 +0000 Subject: [PATCH 7/8] Request Header String Pool --- .../Http/Frame.cs | 18 ++-- ...ns.cs => MemoryPoolIterator2Extensions.cs} | 76 ++++++++++++--- .../Infrastructure/StringPool.cs | 93 +++++++++++++++++++ .../AsciiDecoder.cs | 6 +- .../FrameTests.cs | 2 +- 5 files changed, 169 insertions(+), 26 deletions(-) rename src/Microsoft.AspNet.Server.Kestrel/Infrastructure/{MemoryPoolIterator2Extenstions.cs => MemoryPoolIterator2Extensions.cs} (71%) create mode 100644 src/Microsoft.AspNet.Server.Kestrel/Infrastructure/StringPool.cs diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs index e445bc815..4e8d7b955 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Http/Frame.cs @@ -70,6 +70,8 @@ public partial class Frame : FrameContext, IFrameControl private readonly IPEndPoint _remoteEndPoint; private readonly Action _prepareRequest; + private readonly StringPool _stringPool = new StringPool(); + public Frame(ConnectionContext context) : this(context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null) { @@ -266,6 +268,8 @@ public async Task RequestProcessingAsync() var terminated = false; while (!terminated && !_requestProcessingStopping) { + _stringPool.MarkStart(); + while (!terminated && !_requestProcessingStopping && !TakeStartLine(SocketInput)) { terminated = SocketInput.RemoteIntakeFin; @@ -275,7 +279,7 @@ public async Task RequestProcessingAsync() } } - while (!terminated && !_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders)) + while (!terminated && !_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders, _stringPool)) { terminated = SocketInput.RemoteIntakeFin; if (!terminated) @@ -704,7 +708,7 @@ private bool TakeStartLine(SocketInput input) { return false; } - var method = begin.GetAsciiString(scan); + var method = begin.GetAsciiString(scan, _stringPool); scan.Take(); begin = scan; @@ -728,7 +732,7 @@ private bool TakeStartLine(SocketInput input) { return false; } - queryString = begin.GetAsciiString(scan); + queryString = begin.GetAsciiString(scan, _stringPool); } scan.Take(); @@ -737,7 +741,7 @@ private bool TakeStartLine(SocketInput input) { return false; } - var httpVersion = begin.GetAsciiString(scan); + var httpVersion = begin.GetAsciiString(scan, _stringPool); scan.Take(); if (scan.Take() != '\n') @@ -758,7 +762,7 @@ private bool TakeStartLine(SocketInput input) else { // URI wasn't encoded, parse as ASCII - requestUrlPath = pathBegin.GetAsciiString(pathEnd); + requestUrlPath = pathBegin.GetAsciiString(pathEnd, _stringPool); } consumed = scan; @@ -775,7 +779,7 @@ private bool TakeStartLine(SocketInput input) } } - public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders) + public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders, StringPool stringPool) { var scan = input.ConsumingStart(); var consumed = scan; @@ -866,7 +870,7 @@ public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders req } var name = beginName.GetArraySegment(endName); - var value = beginValue.GetAsciiString(endValue); + var value = beginValue.GetAsciiString(endValue, stringPool); if (wrapping) { value = value.Replace("\r\n", " "); diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extenstions.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extensions.cs similarity index 71% rename from src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extenstions.cs rename to src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extensions.cs index d46eee028..395a3f85b 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extenstions.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extensions.cs @@ -6,51 +6,88 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure { - public static class MemoryPoolIterator2Extenstions + public static class MemoryPoolIterator2Extensions { private const int _maxStackAllocBytes = 16384; private static Encoding _utf8 = Encoding.UTF8; + private static ulong _startHash; - private static unsafe string GetAsciiStringStack(byte[] input, int inputOffset, int length) + // hash bits = random _startHash xor + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // | length & 0xff << 56 | |------ xor ((byte << (index & 0xf) << 2) & 0xffffff) << 32) ------| + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // |----------------------- xor (byte << ((index << 3) & 0x1f)) -----------------------| + + static MemoryPoolIterator2Extensions() + { + using (var rnd = System.Security.Cryptography.RandomNumberGenerator.Create()) + { + var randomBytes = new byte[8]; + rnd.GetBytes(randomBytes); + _startHash = + ((ulong)randomBytes[0]) | + (((ulong)randomBytes[1]) << 8) | + (((ulong)randomBytes[2]) << 16) | + (((ulong)randomBytes[3]) << 24) | + (((ulong)randomBytes[4]) << 32) | + (((ulong)randomBytes[5]) << 40) | + (((ulong)randomBytes[6]) << 48) | + (((ulong)randomBytes[7]) << 56); + } + } + + private static unsafe string GetAsciiStringStack(byte[] input, int inputOffset, int length, StringPool stringPool) { // avoid declaring other local vars, or doing work with stackalloc // to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279 char* output = stackalloc char[length]; - return GetAsciiStringImplementation(output, input, inputOffset, length); + return GetAsciiStringImplementation(output, input, inputOffset, length, stringPool); } - private static unsafe string GetAsciiStringImplementation(char* output, byte[] input, int inputOffset, int length) + private static unsafe string GetAsciiStringImplementation(char* output, byte[] input, int inputOffset, int length, StringPool stringPool) { + var hash = _startHash ^ (((ulong)length & 0xff) << 56); + for (var i = 0; i < length; i++) { - output[i] = (char)input[inputOffset + i]; + var b = input[inputOffset + i]; + output[i] = (char)b; + + hash ^= (((ulong)(b << ((i & 0xf) << 2)) & 0xffffff) << 32) | ((ulong)b << ((i << 3) & 0x1f)); + } + + if (stringPool != null) + { + return stringPool.GetString(hash, output, length); } return new string(output, 0, length); } - private static unsafe string GetAsciiStringStack(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length) + private static unsafe string GetAsciiStringStack(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, StringPool stringPool) { // avoid declaring other local vars, or doing work with stackalloc // to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279 char* output = stackalloc char[length]; - return GetAsciiStringImplementation(output, start, end, inputOffset, length); + return GetAsciiStringImplementation(output, start, end, inputOffset, length, stringPool); } - private unsafe static string GetAsciiStringHeap(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length) + private unsafe static string GetAsciiStringHeap(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, StringPool stringPool) { var buffer = new char[length]; fixed (char* output = buffer) { - return GetAsciiStringImplementation(output, start, end, inputOffset, length); + return GetAsciiStringImplementation(output, start, end, inputOffset, length, stringPool); } } - private static unsafe string GetAsciiStringImplementation(char* output, MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length) + private static unsafe string GetAsciiStringImplementation(char* output, MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, StringPool stringPool) { + var hash = _startHash ^ (((ulong)length & 0xff) << 56); + var outputOffset = 0; var block = start; var remaining = length; @@ -67,7 +104,11 @@ private static unsafe string GetAsciiStringImplementation(char* output, MemoryPo var input = block.Array; for (var i = 0; i < following; i++) { - output[i + outputOffset] = (char)input[i + inputOffset]; + var b = input[inputOffset + i]; + + output[i + outputOffset] = (char)b; + + hash ^= (((ulong)(b << ((i & 0xf) << 2)) & 0xffffff) << 32) | ((ulong)b << ((i << 3) & 0x1f)); } remaining -= following; @@ -76,6 +117,11 @@ private static unsafe string GetAsciiStringImplementation(char* output, MemoryPo if (remaining == 0) { + if (stringPool != null) + { + return stringPool.GetString(hash, output, length); + } + return new string(output, 0, length); } @@ -84,7 +130,7 @@ private static unsafe string GetAsciiStringImplementation(char* output, MemoryPo } } - public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end) + public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end, StringPool stringPool) { if (start.IsDefault || end.IsDefault) { @@ -98,15 +144,15 @@ public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIt // https://tools.ietf.org/html/rfc7230#section-3.2.4 if (end.Block == start.Block) { - return GetAsciiStringStack(start.Block.Array, start.Index, length); + return GetAsciiStringStack(start.Block.Array, start.Index, length, stringPool); } if (length > _maxStackAllocBytes) { - return GetAsciiStringHeap(start.Block, end, start.Index, length); + return GetAsciiStringHeap(start.Block, end, start.Index, length, stringPool); } - return GetAsciiStringStack(start.Block, end, start.Index, length); + return GetAsciiStringStack(start.Block, end, start.Index, length, stringPool); } public static string GetUtf8String(this MemoryPoolIterator2 start, MemoryPoolIterator2 end) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/StringPool.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/StringPool.cs new file mode 100644 index 000000000..8f31d8354 --- /dev/null +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/StringPool.cs @@ -0,0 +1,93 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Server.Kestrel.Infrastructure +{ + public class StringPool + { + private const int _maxCached = 18; + private const int _maxCacheLength = 256; + + private readonly ulong[] _hashes = new ulong[_maxCached]; + private readonly int[] _lastUse = new int[_maxCached]; + private readonly string[] _strings = new string[_maxCached]; + + private int _currentUse = 0; + + public void MarkStart() + { + _currentUse++; + } + + public unsafe string GetString(ulong hash, char* data, int length) + { + if (length > _maxCacheLength) + { + return new string(data, 0, length); + } + + int oldestEntry = int.MaxValue; + int oldestIndex = 0; + + for (var i = 0; i < _maxCached; i++) + { + var usage = _lastUse[i]; + if (oldestEntry > usage) + { + oldestEntry = usage; + oldestIndex = i; + } + + if (hash == _hashes[i]) + { + var cachedString = _strings[i]; + if (cachedString.Length != length) + { + continue; + } + + fixed(char* cs = cachedString) + { + var cached = cs; + var potential = data; + + var c = 0; + var lengthMinusSpan = length - 3; + for (; c < lengthMinusSpan; c += 4) + { + if( + *(cached) != *(potential) || + *(cached + 1) != *(potential + 1) || + *(cached + 2) != *(potential + 2) || + *(cached + 3) != *(potential + 3) + ) + { + continue; + } + cached += 4; + potential += 4; + } + for (; c < length; c++) + { + if (*(cached++) != *(potential++)) + { + continue; + } + } + } + + _lastUse[i] = _currentUse; + // same string + return cachedString; + } + } + + var value = new string(data, 0, length); + _lastUse[oldestIndex] = _currentUse; + _hashes[oldestIndex] = hash; + _strings[oldestIndex] = value; + + return value; + } + } +} diff --git a/test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs b/test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs index 26c034d2d..bed87de3e 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs @@ -21,7 +21,7 @@ private void FullByteRangeSupported() var begin = mem.GetIterator(); var end = GetIterator(begin, byteRange.Length); - var s = begin.GetAsciiString(end); + var s = begin.GetAsciiString(end, null); Assert.Equal(s.Length, byteRange.Length); @@ -60,7 +60,7 @@ private void MultiBlockProducesCorrectResults() var begin = mem0.GetIterator(); var end = GetIterator(begin, expectedByteRange.Length); - var s = begin.GetAsciiString(end); + var s = begin.GetAsciiString(end, null); Assert.Equal(s.Length, expectedByteRange.Length); @@ -89,7 +89,7 @@ private void HeapAllocationProducesCorrectResults() var begin = mem0.GetIterator(); var end = GetIterator(begin, expectedByteRange.Length); - var s = begin.GetAsciiString(end); + var s = begin.GetAsciiString(end, null); Assert.Equal(s.Length, expectedByteRange.Length); diff --git a/test/Microsoft.AspNet.Server.KestrelTests/FrameTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/FrameTests.cs index 8266a1c2e..4d33fcc20 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/FrameTests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/FrameTests.cs @@ -56,7 +56,7 @@ public void EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders) Buffer.BlockCopy(headerArray, 0, inputBuffer.Data.Array, inputBuffer.Data.Offset, headerArray.Length); socketInput.IncomingComplete(headerArray.Length, null); - var success = Frame.TakeMessageHeaders(socketInput, headerCollection); + var success = Frame.TakeMessageHeaders(socketInput, headerCollection, null); Assert.True(success); Assert.Equal(numHeaders, headerCollection.Count()); From 9b940dcbfa93ed44d9082c5184ad375ee7e21346 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 24 Nov 2015 15:23:27 +0000 Subject: [PATCH 8/8] Simpler StringPool hashing --- .../MemoryPoolIterator2Extensions.cs | 53 ++++++++----------- .../Infrastructure/StringPool.cs | 27 ++++++++-- .../AsciiDecoder.cs | 9 ++-- .../FrameTests.cs | 3 +- 4 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extensions.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extensions.cs index 395a3f85b..1f3b16f57 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extensions.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolIterator2Extensions.cs @@ -11,13 +11,7 @@ public static class MemoryPoolIterator2Extensions private const int _maxStackAllocBytes = 16384; private static Encoding _utf8 = Encoding.UTF8; - private static ulong _startHash; - - // hash bits = random _startHash xor - // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 - // | length & 0xff << 56 | |------ xor ((byte << (index & 0xf) << 2) & 0xffffff) << 32) ------| - // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 - // |----------------------- xor (byte << ((index << 3) & 0x1f)) -----------------------| + private static uint _startHash; static MemoryPoolIterator2Extensions() { @@ -26,14 +20,10 @@ static MemoryPoolIterator2Extensions() var randomBytes = new byte[8]; rnd.GetBytes(randomBytes); _startHash = - ((ulong)randomBytes[0]) | - (((ulong)randomBytes[1]) << 8) | - (((ulong)randomBytes[2]) << 16) | - (((ulong)randomBytes[3]) << 24) | - (((ulong)randomBytes[4]) << 32) | - (((ulong)randomBytes[5]) << 40) | - (((ulong)randomBytes[6]) << 48) | - (((ulong)randomBytes[7]) << 56); + (randomBytes[0]) | + (((uint)randomBytes[1]) << 8) | + (((uint)randomBytes[2]) << 16) | + (((uint)randomBytes[3]) << 24); } } @@ -47,22 +37,19 @@ private static unsafe string GetAsciiStringStack(byte[] input, int inputOffset, } private static unsafe string GetAsciiStringImplementation(char* output, byte[] input, int inputOffset, int length, StringPool stringPool) { - var hash = _startHash ^ (((ulong)length & 0xff) << 56); + var hash = _startHash; for (var i = 0; i < length; i++) { var b = input[inputOffset + i]; output[i] = (char)b; - hash ^= (((ulong)(b << ((i & 0xf) << 2)) & 0xffffff) << 32) | ((ulong)b << ((i << 3) & 0x1f)); - } - - if (stringPool != null) - { - return stringPool.GetString(hash, output, length); + // give greater hash fidelity to 7 bit ascii + // rotate https://github.com/dotnet/coreclr/pull/2027 + hash = ((hash << 7) | (hash >> (32 - 7))) ^ b; } - return new string(output, 0, length); + return stringPool.GetString(hash, output, length); } private static unsafe string GetAsciiStringStack(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, StringPool stringPool) @@ -86,7 +73,7 @@ private unsafe static string GetAsciiStringHeap(MemoryPoolBlock2 start, MemoryPo private static unsafe string GetAsciiStringImplementation(char* output, MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, StringPool stringPool) { - var hash = _startHash ^ (((ulong)length & 0xff) << 56); + var hash = _startHash; var outputOffset = 0; var block = start; @@ -108,7 +95,9 @@ private static unsafe string GetAsciiStringImplementation(char* output, MemoryPo output[i + outputOffset] = (char)b; - hash ^= (((ulong)(b << ((i & 0xf) << 2)) & 0xffffff) << 32) | ((ulong)b << ((i << 3) & 0x1f)); + // give greater hash fidelity to 7 bit ascii + // rotate https://github.com/dotnet/coreclr/pull/2027 + hash = ((hash << 7) | (hash >> (32 - 7))) ^ b; } remaining -= following; @@ -117,17 +106,14 @@ private static unsafe string GetAsciiStringImplementation(char* output, MemoryPo if (remaining == 0) { - if (stringPool != null) - { - return stringPool.GetString(hash, output, length); - } - - return new string(output, 0, length); + break; } block = block.Next; inputOffset = block.Start; } + + return stringPool.GetString(hash, output, length); } public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end, StringPool stringPool) @@ -139,6 +125,11 @@ public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIt var length = start.GetLength(end); + if (length == 0) + { + return null; + } + // Bytes out of the range of ascii are treated as "opaque data" // and kept in string as a char value that casts to same input byte value // https://tools.ietf.org/html/rfc7230#section-3.2.4 diff --git a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/StringPool.cs b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/StringPool.cs index 8f31d8354..7dd4c4604 100644 --- a/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/StringPool.cs +++ b/src/Microsoft.AspNet.Server.Kestrel/Infrastructure/StringPool.cs @@ -1,14 +1,19 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; + namespace Microsoft.AspNet.Server.Kestrel.Infrastructure { public class StringPool { - private const int _maxCached = 18; + // x64 int array byte size (28 + length * 4) rounded up to 8 bytes + // x86 int array byte size (12 + length * 4) rounded up to 4 bytes + // Array of 31 ints is 2 consecutive cache lines on x64; second prefetched + private const int _maxCached = 31; private const int _maxCacheLength = 256; - private readonly ulong[] _hashes = new ulong[_maxCached]; + private readonly uint[] _hashes = new uint[_maxCached]; private readonly int[] _lastUse = new int[_maxCached]; private readonly string[] _strings = new string[_maxCached]; @@ -19,7 +24,7 @@ public void MarkStart() _currentUse++; } - public unsafe string GetString(ulong hash, char* data, int length) + public unsafe string GetString(uint hash, char* data, int length) { if (length > _maxCacheLength) { @@ -43,6 +48,9 @@ public unsafe string GetString(ulong hash, char* data, int length) var cachedString = _strings[i]; if (cachedString.Length != length) { +#if DEBUG + Console.WriteLine($"{nameof(StringPool)} Collision differing lengths {cachedString.Length} and {length}"); +#endif continue; } @@ -62,6 +70,9 @@ public unsafe string GetString(ulong hash, char* data, int length) *(cached + 3) != *(potential + 3) ) { +#if DEBUG + Console.WriteLine($"{nameof(StringPool)} Collision same length, differing strings"); +#endif continue; } cached += 4; @@ -71,6 +82,9 @@ public unsafe string GetString(ulong hash, char* data, int length) { if (*(cached++) != *(potential++)) { +#if DEBUG + Console.WriteLine($"{nameof(StringPool)} Collision same length, differing strings"); +#endif continue; } } @@ -83,6 +97,13 @@ public unsafe string GetString(ulong hash, char* data, int length) } var value = new string(data, 0, length); +#if DEBUG + if (_lastUse[oldestIndex] != 0) + { + Console.WriteLine($"{nameof(StringPool)} Evict: {_strings[oldestIndex]} {_lastUse[oldestIndex]} {_hashes[oldestIndex]}"); + Console.WriteLine($"{nameof(StringPool)} New: {value} {_currentUse} {hash}"); + } +#endif _lastUse[oldestIndex] = _currentUse; _hashes[oldestIndex] = hash; _strings[oldestIndex] = value; diff --git a/test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs b/test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs index bed87de3e..f9dfff42c 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs @@ -13,6 +13,7 @@ public class AsciiDecoderTests [Fact] private void FullByteRangeSupported() { + var stringPool = new StringPool(); var byteRange = Enumerable.Range(0, 255).Select(x => (byte)x).ToArray(); var mem = MemoryPoolBlock2.Create(new ArraySegment(byteRange), IntPtr.Zero, null, null); @@ -21,7 +22,7 @@ private void FullByteRangeSupported() var begin = mem.GetIterator(); var end = GetIterator(begin, byteRange.Length); - var s = begin.GetAsciiString(end, null); + var s = begin.GetAsciiString(end, stringPool); Assert.Equal(s.Length, byteRange.Length); @@ -37,6 +38,7 @@ private void FullByteRangeSupported() [Fact] private void MultiBlockProducesCorrectResults() { + var stringPool = new StringPool(); var byteRange = Enumerable.Range(0, 512 + 64).Select(x => (byte)x).ToArray(); var expectedByteRange = byteRange .Concat(byteRange) @@ -60,7 +62,7 @@ private void MultiBlockProducesCorrectResults() var begin = mem0.GetIterator(); var end = GetIterator(begin, expectedByteRange.Length); - var s = begin.GetAsciiString(end, null); + var s = begin.GetAsciiString(end, stringPool); Assert.Equal(s.Length, expectedByteRange.Length); @@ -76,6 +78,7 @@ private void MultiBlockProducesCorrectResults() [Fact] private void HeapAllocationProducesCorrectResults() { + var stringPool = new StringPool(); var byteRange = Enumerable.Range(0, 16384 + 64).Select(x => (byte)x).ToArray(); var expectedByteRange = byteRange.Concat(byteRange).ToArray(); @@ -89,7 +92,7 @@ private void HeapAllocationProducesCorrectResults() var begin = mem0.GetIterator(); var end = GetIterator(begin, expectedByteRange.Length); - var s = begin.GetAsciiString(end, null); + var s = begin.GetAsciiString(end, stringPool); Assert.Equal(s.Length, expectedByteRange.Length); diff --git a/test/Microsoft.AspNet.Server.KestrelTests/FrameTests.cs b/test/Microsoft.AspNet.Server.KestrelTests/FrameTests.cs index 4d33fcc20..f3161cc1d 100644 --- a/test/Microsoft.AspNet.Server.KestrelTests/FrameTests.cs +++ b/test/Microsoft.AspNet.Server.KestrelTests/FrameTests.cs @@ -48,6 +48,7 @@ public void ChunkedPrefixMustBeHexCrLfWithoutLeadingZeros(int dataCount, string [InlineData("Connection:\r\n \r\nCookie \r\n", 1)] public void EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders) { + var stringPool = new StringPool(); var socketInput = new SocketInput(new MemoryPool2()); var headerCollection = new FrameRequestHeaders(); @@ -56,7 +57,7 @@ public void EmptyHeaderValuesCanBeParsed(string rawHeaders, int numHeaders) Buffer.BlockCopy(headerArray, 0, inputBuffer.Data.Array, inputBuffer.Data.Offset, headerArray.Length); socketInput.IncomingComplete(headerArray.Length, null); - var success = Frame.TakeMessageHeaders(socketInput, headerCollection, null); + var success = Frame.TakeMessageHeaders(socketInput, headerCollection, stringPool); Assert.True(success); Assert.Equal(numHeaders, headerCollection.Count());