Improved Memoryblock tracking

Also fixed 3 tests that weren't handling block properly
This commit is contained in:
Ben Adams 2016-07-20 11:55:14 +01:00
parent 3078f4914c
commit 7b8abf5a3e
6 changed files with 69 additions and 8 deletions

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{ {
@ -60,20 +61,44 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
/// </summary> /// </summary>
private bool _disposedValue = false; // To detect redundant calls private bool _disposedValue = false; // To detect redundant calls
#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
/// <summary> /// <summary>
/// Called to take a block from the pool. /// Called to take a block from the pool.
/// </summary> /// </summary>
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns> /// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
public MemoryPoolBlock Lease() public MemoryPoolBlock Lease()
{ {
#endif
MemoryPoolBlock block; MemoryPoolBlock block;
if (_blocks.TryDequeue(out block)) if (_blocks.TryDequeue(out block))
{ {
// block successfully taken from the stack - return it // block successfully taken from the stack - return it
#if DEBUG
block.Leaser = memberName + ", " + sourceFilePath + ", " + sourceLineNumber;
block.IsLeased = true;
#if !NETSTANDARD1_3
block.StackTrace = new StackTrace(true).ToString();
#endif
#endif
return block; return block;
} }
// no blocks available - grow the pool // no blocks available - grow the pool
return AllocateSlab(); block = AllocateSlab();
#if DEBUG
block.Leaser = memberName + ", " + sourceFilePath + ", " + sourceLineNumber;
block.IsLeased = true;
#if !NETSTANDARD1_3
block.StackTrace = new StackTrace(true).ToString();
#endif
#endif
return block;
} }
/// <summary> /// <summary>
@ -100,6 +125,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
basePtr, basePtr,
this, this,
slab); slab);
#if DEBUG
block.IsLeased = true;
#endif
Return(block); Return(block);
} }
@ -123,19 +151,33 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
/// <param name="block">The block to return. It must have been acquired by calling Lease on the same memory pool instance.</param> /// <param name="block">The block to return. It must have been acquired by calling Lease on the same memory pool instance.</param>
public void Return(MemoryPoolBlock block) public void Return(MemoryPoolBlock block)
{ {
#if DEBUG
Debug.Assert(block.Pool == this, "Returned block was not leased from this pool"); 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 + "\n" + block.StackTrace);
block.IsLeased = false;
#endif
if (block.Slab != null && block.Slab.IsActive) if (block.Slab != null && block.Slab.IsActive)
{ {
block.Reset(); block.Reset();
_blocks.Enqueue(block); _blocks.Enqueue(block);
} }
else
{
GC.SuppressFinalize(block);
}
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!_disposedValue) if (!_disposedValue)
{ {
_disposedValue = true;
#if DEBUG
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
#endif
if (disposing) if (disposing)
{ {
MemoryPoolSlab slab; MemoryPoolSlab slab;
@ -146,7 +188,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); GC.SuppressFinalize(block);
} }
@ -155,7 +199,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
// N/A: set large fields to null. // N/A: set large fields to null.
_disposedValue = true;
} }
} }

View File

@ -71,10 +71,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
/// </summary> /// </summary>
public MemoryPoolBlock Next; public MemoryPoolBlock Next;
#if DEBUG
public bool IsLeased { get; set; }
public string Leaser { get; set; }
public string StackTrace { get; set; }
#endif
~MemoryPoolBlock() ~MemoryPoolBlock()
{ {
Debug.Assert(Slab == null || !Slab.IsActive, "Block being garbage collected instead of returned to pool"); #if DEBUG
Debug.Assert(Slab == null || !Slab.IsActive, "Block being garbage collected instead of returned to pool: " + Leaser + "\n" + StackTrace);
#endif
if (Slab != null && Slab.IsActive) if (Slab != null && Slab.IsActive)
{ {
Pool.Return(new MemoryPoolBlock(DataArrayPtr) Pool.Return(new MemoryPoolBlock(DataArrayPtr)

View File

@ -61,6 +61,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
{ {
if (!_disposedValue) if (!_disposedValue)
{ {
_disposedValue = true;
if (disposing) if (disposing)
{ {
// N/A: dispose managed state (managed objects). // N/A: dispose managed state (managed objects).
@ -72,8 +74,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
// set large fields to null. // set large fields to null.
Array = null; Array = null;
_disposedValue = true;
} }
} }

View File

@ -46,6 +46,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
thread.Stop(TimeSpan.FromSeconds(2.5)); thread.Stop(TimeSpan.FromSeconds(2.5));
} }
Threads.Clear(); Threads.Clear();
#if DEBUG
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
#endif
} }
public IDisposable CreateServer(ServerAddress address) public IDisposable CreateServer(ServerAddress address)

View File

@ -59,6 +59,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var end = GetIterator(begin, byteRange.Length); var end = GetIterator(begin, byteRange.Length);
Assert.Throws<BadHttpRequestException>(() => begin.GetAsciiString(end)); Assert.Throws<BadHttpRequestException>(() => begin.GetAsciiString(end));
pool.Return(mem);
} }
} }
} }
@ -150,7 +152,10 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
var returnBlock = block; var returnBlock = block;
block = block.Next; block = block.Next;
pool.Return(returnBlock); if (returnBlock != mem0 && returnBlock != mem1)
{
pool.Return(returnBlock);
}
} }
pool.Return(mem0); pool.Return(mem0);

View File

@ -317,6 +317,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.ThrowsAny<InvalidOperationException>(() => scan.Skip(8)); Assert.ThrowsAny<InvalidOperationException>(() => scan.Skip(8));
_pool.Return(block); _pool.Return(block);
_pool.Return(nextBlock);
} }
[Theory] [Theory]