// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace System.Buffers { /// /// Used to allocate and distribute re-usable blocks of memory. /// internal class DiagnosticMemoryPool : MemoryPool { private readonly MemoryPool _pool; private readonly bool _allowLateReturn; private readonly bool _rentTracking; private readonly object _syncObj; private readonly HashSet _blocks; private readonly List _blockAccessExceptions; private readonly TaskCompletionSource _allBlocksRetuned; private int _totalBlocks; /// /// This default value passed in to Rent to use the default value for the pool. /// private const int AnySize = -1; public DiagnosticMemoryPool(MemoryPool pool, bool allowLateReturn = false, bool rentTracking = false) { _pool = pool; _allowLateReturn = allowLateReturn; _rentTracking = rentTracking; _blocks = new HashSet(); _syncObj = new object(); _allBlocksRetuned = new TaskCompletionSource(); _blockAccessExceptions = new List(); } public bool IsDisposed { get; private set; } public override IMemoryOwner Rent(int size = AnySize) { lock (_syncObj) { if (IsDisposed) { MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool); } var diagnosticPoolBlock = new DiagnosticPoolBlock(this, _pool.Rent(size)); if (_rentTracking) { diagnosticPoolBlock.Track(); } _totalBlocks++; _blocks.Add(diagnosticPoolBlock); return diagnosticPoolBlock; } } public override int MaxBufferSize => _pool.MaxBufferSize; internal void Return(DiagnosticPoolBlock block) { bool returnedAllBlocks; lock (_syncObj) { _blocks.Remove(block); returnedAllBlocks = _blocks.Count == 0; } if (IsDisposed) { if (!_allowLateReturn) { MemoryPoolThrowHelper.ThrowInvalidOperationException_BlockReturnedToDisposedPool(block); } if (returnedAllBlocks) { SetAllBlocksReturned(); } } } internal void ReportException(Exception exception) { lock (_syncObj) { _blockAccessExceptions.Add(exception); } } protected override void Dispose(bool disposing) { if (IsDisposed) { MemoryPoolThrowHelper.ThrowInvalidOperationException_DoubleDispose(); } bool allBlocksReturned = false; try { lock (_syncObj) { IsDisposed = true; allBlocksReturned = _blocks.Count == 0; if (!allBlocksReturned && !_allowLateReturn) { MemoryPoolThrowHelper.ThrowInvalidOperationException_DisposingPoolWithActiveBlocks(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray()); } if (_blockAccessExceptions.Any()) { throw CreateAccessExceptions(); } } } finally { if (allBlocksReturned) { SetAllBlocksReturned(); } } } private void SetAllBlocksReturned() { if (_blockAccessExceptions.Any()) { _allBlocksRetuned.SetException(CreateAccessExceptions()); } else { _allBlocksRetuned.SetResult(null); } } private AggregateException CreateAccessExceptions() { return new AggregateException("Exceptions occurred while accessing blocks", _blockAccessExceptions.ToArray()); } public async Task WhenAllBlocksReturnedAsync(TimeSpan timeout) { var task = await Task.WhenAny(_allBlocksRetuned.Task, Task.Delay(timeout)); if (task != _allBlocksRetuned.Task) { MemoryPoolThrowHelper.ThrowInvalidOperationException_BlocksWereNotReturnedInTime(_totalBlocks - _blocks.Count, _totalBlocks, _blocks.ToArray()); } await task; } } }