File: DiagnosticMemoryPoolTests.cs
Web Access
Project: src\src\Servers\Kestrel\Core\test\Microsoft.AspNetCore.Server.Kestrel.Core.Tests.csproj (Microsoft.AspNetCore.Server.Kestrel.Core.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
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<byte> CreatePool() => new DiagnosticMemoryPool(new PinnedBlockMemoryPool());
 
    [Fact]
    public void DoubleDisposeThrows()
    {
        var memoryPool = CreatePool();
        memoryPool.Dispose();
        var exception = Assert.Throws<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => 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<InvalidOperationException>(() => MemoryMarshal.TryGetArray<byte>(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<ObjectDisposedException>(() => 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<ObjectDisposedException>(() => 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<ObjectDisposedException>(() => MemoryMarshal.TryGetArray<byte>(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 PinnedBlockMemoryPool(), 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 PinnedBlockMemoryPool(), allowLateReturn: true);
        var block = memoryPool.Rent();
        memoryPool.Dispose();
 
        var exception = Assert.Throws<InvalidOperationException>(() => block.Memory);
        Assert.Equal("Block is backed by disposed slab", exception.Message);
 
        block.Dispose();
        var aggregateException = await Assert.ThrowsAsync<AggregateException>(async () => await memoryPool.WhenAllBlocksReturnedAsync(TimeSpan.FromSeconds(5)));
 
        Assert.Equal(new Exception[] { exception }, aggregateException.InnerExceptions);
    }
 
    [Fact]
    public void ExceptionsContainStackTraceWhenEnabled()
    {
        var memoryPool = new DiagnosticMemoryPool(new PinnedBlockMemoryPool(), rentTracking: true);
        var block = memoryPool.Rent();
 
        ExpectDisposeException(memoryPool);
 
        var exception = Assert.Throws<InvalidOperationException>(() => block.Memory);
        Assert.Contains("Block is backed by disposed slab", exception.Message);
        Assert.Contains("ExceptionsContainStackTraceWhenEnabled", exception.Message);
    }
 
    private static void ExpectDisposeException(MemoryPool<byte> memoryPool)
    {
        var exception = Assert.Throws<InvalidOperationException>(() => memoryPool.Dispose());
        Assert.Contains("Memory pool with active blocks is being disposed, 0 of 1 returned", exception.Message);
    }
 
    private static void ExpectDisposeAggregateException(MemoryPool<byte> memoryPool, params Exception[] inner)
    {
        var exception = Assert.Throws<AggregateException>(() => memoryPool.Dispose());
 
        Assert.Equal(inner, exception.InnerExceptions);
    }
}