// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Buffers; using System.Runtime.InteropServices; using System.Threading.Tasks; using Xunit; namespace Microsoft.Extensions.Internal.Test { public class DiagnosticMemoryPoolTests: MemoryPoolTests { protected override MemoryPool CreatePool() => new DiagnosticMemoryPool(new SlabMemoryPool()); [Fact] public void DoubleDisposeThrows() { var memoryPool = CreatePool(); memoryPool.Dispose(); var exception = Assert.Throws(() => memoryPool.Dispose()); Assert.Equal("Object is being disposed twice", exception.Message); } [Fact] public void DisposeWithActiveBlocksThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); ExpectDisposeException(memoryPool); var exception = Assert.Throws(() => block.Dispose()); Assert.Equal("Block is being returned to disposed pool", exception.Message); } [Fact] public void DoubleBlockDisposeThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); block.Dispose(); var exception = Assert.Throws(() => block.Dispose()); Assert.Equal("Block is being disposed twice", exception.Message); ExpectDisposeAggregateException(memoryPool, exception); } [Fact] public void GetMemoryOfDisposedPoolThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); ExpectDisposeException(memoryPool); var exception = Assert.Throws(() => block.Memory); Assert.Equal("Block is backed by disposed slab", exception.Message); } [Fact] public void GetMemoryPinOfDisposedPoolThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); var memory = block.Memory; ExpectDisposeException(memoryPool); var exception = Assert.Throws(() => memory.Pin()); Assert.Equal("Block is backed by disposed slab", exception.Message); } [Fact] public void GetMemorySpanOfDisposedPoolThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); var memory = block.Memory; ExpectDisposeException(memoryPool); var threw = false; try { _ = memory.Span; } catch (InvalidOperationException ode) { threw = true; Assert.Equal("Block is backed by disposed slab", ode.Message); } Assert.True(threw); } [Fact] public void GetMemoryTryGetArrayOfDisposedPoolThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); var memory = block.Memory; ExpectDisposeException(memoryPool); var exception = Assert.Throws(() => MemoryMarshal.TryGetArray(memory, out _)); Assert.Equal("Block is backed by disposed slab", exception.Message); } [Fact] public void GetMemoryOfDisposedThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); block.Dispose(); var exception = Assert.Throws(() => block.Memory); Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPoolBlock'.", exception.Message); ExpectDisposeAggregateException(memoryPool, exception); } [Fact] public void GetMemoryPinOfDisposedThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); var memory = block.Memory; block.Dispose(); var exception = Assert.Throws(() => memory.Pin()); Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPoolBlock'.", exception.Message); ExpectDisposeAggregateException(memoryPool, exception); } [Fact] public void GetMemorySpanOfDisposedThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); var memory = block.Memory; block.Dispose(); Exception exception = null; try { _ = memory.Span; } catch (ObjectDisposedException ode) { exception = ode; Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPoolBlock'.", ode.Message); } Assert.NotNull(exception); ExpectDisposeAggregateException(memoryPool, exception); } [Fact] public void GetMemoryTryGetArrayOfDisposedThrows() { var memoryPool = CreatePool(); var block = memoryPool.Rent(); var memory = block.Memory; block.Dispose(); var exception = Assert.Throws(() => MemoryMarshal.TryGetArray(memory, out _)); Assert.Equal($"Cannot access a disposed object.{Environment.NewLine}Object name: 'MemoryPoolBlock'.", exception.Message); ExpectDisposeAggregateException(memoryPool, exception); } [Fact] public async Task DoesNotThrowWithLateReturns() { var memoryPool = new DiagnosticMemoryPool(new SlabMemoryPool(), allowLateReturn: true); var block = memoryPool.Rent(); memoryPool.Dispose(); block.Dispose(); await memoryPool.WhenAllBlocksReturnedAsync(TimeSpan.FromSeconds(5)); } [Fact] public async Task ThrowsOnAccessToLateBlocks() { var memoryPool = new DiagnosticMemoryPool(new SlabMemoryPool(), allowLateReturn: true); var block = memoryPool.Rent(); memoryPool.Dispose(); var exception = Assert.Throws(() => block.Memory); Assert.Equal("Block is backed by disposed slab", exception.Message); block.Dispose(); var aggregateException = await Assert.ThrowsAsync(async () => await memoryPool.WhenAllBlocksReturnedAsync(TimeSpan.FromSeconds(5))); Assert.Equal(new Exception [] { exception }, aggregateException.InnerExceptions); } [Fact] public void ExceptionsContainStackTraceWhenEnabled() { var memoryPool = new DiagnosticMemoryPool(new SlabMemoryPool(), rentTracking: true); var block = memoryPool.Rent(); ExpectDisposeException(memoryPool); var exception = Assert.Throws(() => block.Memory); Assert.Contains("Block is backed by disposed slab", exception.Message); Assert.Contains("ExceptionsContainStackTraceWhenEnabled", exception.Message); } private static void ExpectDisposeException(MemoryPool memoryPool) { var exception = Assert.Throws(() => memoryPool.Dispose()); Assert.Contains("Memory pool with active blocks is being disposed, 0 of 1 returned", exception.Message); } private static void ExpectDisposeAggregateException(MemoryPool memoryPool, params Exception[] inner) { var exception = Assert.Throws(() => memoryPool.Dispose()); Assert.Equal(inner, exception.InnerExceptions); } } }