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]