diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPool.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPool.cs index 4641206872..260cfc1f1e 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPool.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPool.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure { @@ -64,16 +65,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure /// Called to take a block from the pool. /// /// The block that is reserved for the called. It must be passed to Return when it is no longer being used. +#if DEBUG + public MemoryPoolBlock Lease( + [CallerMemberName] string memberName = "", + [CallerFilePath] string sourceFilePath = "", + [CallerLineNumber] int sourceLineNumber = 0) + { + Debug.Assert(!_disposedValue, "Block being leased from disposed pool!"); +#else public MemoryPoolBlock Lease() { +#endif MemoryPoolBlock block; if (_blocks.TryDequeue(out block)) { // block successfully taken from the stack - return it +#if DEBUG + block.Leaser = memberName + ", " + sourceFilePath + ", " + sourceLineNumber; + block.IsLeased = true; +#endif return block; } // no blocks available - grow the pool - return AllocateSlab(); + block = AllocateSlab(); +#if DEBUG + block.Leaser = memberName + ", " + sourceFilePath + ", " + sourceLineNumber; + block.IsLeased = true; +#endif + return block; } /// @@ -100,6 +119,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure basePtr, this, slab); +#if DEBUG + block.IsLeased = true; +#endif Return(block); } @@ -123,19 +145,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure /// The block to return. It must have been acquired by calling Lease on the same memory pool instance. public void Return(MemoryPoolBlock block) { +#if DEBUG Debug.Assert(block.Pool == this, "Returned block was not leased from this pool"); + Debug.Assert(block.IsLeased, $"Block being returned to pool twice: {block.Leaser}{Environment.NewLine}"); + block.IsLeased = false; +#endif if (block.Slab != null && block.Slab.IsActive) { block.Reset(); _blocks.Enqueue(block); } + else + { + GC.SuppressFinalize(block); + } } protected virtual void Dispose(bool disposing) { if (!_disposedValue) { + _disposedValue = true; +#if DEBUG + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); +#endif if (disposing) { MemoryPoolSlab slab; @@ -146,7 +182,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure } } - foreach (var block in _blocks) + // Discard blocks in pool + MemoryPoolBlock block; + while (_blocks.TryDequeue(out block)) { GC.SuppressFinalize(block); } @@ -155,7 +193,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure // N/A: set large fields to null. - _disposedValue = true; } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolBlock.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolBlock.cs index f2ad31cf58..5f22dd4a1f 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolBlock.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolBlock.cs @@ -71,10 +71,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure /// public MemoryPoolBlock Next; +#if DEBUG + public bool IsLeased { get; set; } + public string Leaser { get; set; } +#endif + ~MemoryPoolBlock() { - Debug.Assert(Slab == null || !Slab.IsActive, "Block being garbage collected instead of returned to pool"); - +#if DEBUG + Debug.Assert(Slab == null || !Slab.IsActive, $"{Environment.NewLine}{Environment.NewLine}*** Block being garbage collected instead of returned to pool: {Leaser} ***{Environment.NewLine}"); +#endif if (Slab != null && Slab.IsActive) { Pool.Return(new MemoryPoolBlock(DataArrayPtr) diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolSlab.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolSlab.cs index cd8d65476d..709a463789 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolSlab.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolSlab.cs @@ -61,6 +61,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure { if (!_disposedValue) { + _disposedValue = true; + if (disposing) { // N/A: dispose managed state (managed objects). @@ -72,8 +74,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure // set large fields to null. Array = null; - - _disposedValue = true; } } diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/KestrelEngine.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/KestrelEngine.cs index bdc9c70862..c611580ae4 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/KestrelEngine.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/KestrelEngine.cs @@ -46,6 +46,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal Task.WaitAll(Threads.Select(thread => thread.StopAsync(TimeSpan.FromSeconds(2.5))).ToArray()); Threads.Clear(); +#if DEBUG + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); +#endif } public IDisposable CreateServer(ServerAddress address) diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs index 91d368a90b..87990788c6 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/AsciiDecoding.cs @@ -59,6 +59,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var end = GetIterator(begin, byteRange.Length); Assert.Throws(() => begin.GetAsciiString(end)); + + pool.Return(mem); } } } @@ -152,9 +154,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests block = block.Next; pool.Return(returnBlock); } - - pool.Return(mem0); - pool.Return(mem1); } } diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs index b415cda7a3..a8c7e69510 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MemoryPoolIteratorTests.cs @@ -317,6 +317,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests Assert.ThrowsAny(() => scan.Skip(8)); _pool.Return(block); + _pool.Return(nextBlock); } [Theory]