Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 - Limit all memory allocations in the MemoryAllocator layer #2715

Merged
merged 3 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/ImageSharp/Formats/Png/PngDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,9 @@ private void SkipChunkDataAndCrc(in PngChunk chunk)
private IMemoryOwner<byte> ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
// We don't want to throw a degenerated memory exception here as we want to allow partial decoding
// so limit the length.
length = (int)Math.Min(length, this.currentStream.Length - this.currentStream.Position);
IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);

this.currentStream.Read(buffer.GetSpan(), 0, length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Memory.Internals
internal struct UnmanagedMemoryHandle : IEquatable<UnmanagedMemoryHandle>
{
// Number of allocation re-attempts when detecting OutOfMemoryException.
private const int MaxAllocationAttempts = 1000;
private const int MaxAllocationAttempts = 10;

// Track allocations for testing purposes:
private static int totalOutstandingHandles;
Expand Down
47 changes: 41 additions & 6 deletions src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace SixLabors.ImageSharp.Memory
{
Expand All @@ -11,6 +13,8 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
public abstract class MemoryAllocator
{
private const int OneGigabyte = 1 << 30;

/// <summary>
/// Gets the default platform-specific global <see cref="MemoryAllocator"/> instance that
/// serves as the default value for <see cref="Configuration.MemoryAllocator"/>.
Expand All @@ -21,6 +25,10 @@ public abstract class MemoryAllocator
/// </summary>
public static MemoryAllocator Default { get; } = Create();

internal long MemoryGroupAllocationLimitBytes { get; private set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte;

internal int SingleBufferAllocationLimitBytes { get; private set; } = OneGigabyte;

/// <summary>
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes.
/// </summary>
Expand All @@ -31,16 +39,24 @@ public abstract class MemoryAllocator
/// Creates a default instance of a <see cref="MemoryAllocator"/> optimized for the executing platform.
/// </summary>
/// <returns>The <see cref="MemoryAllocator"/>.</returns>
public static MemoryAllocator Create() =>
new UniformUnmanagedMemoryPoolMemoryAllocator(null);
public static MemoryAllocator Create() => Create(default);

/// <summary>
/// Creates the default <see cref="MemoryAllocator"/> using the provided options.
/// </summary>
/// <param name="options">The <see cref="MemoryAllocatorOptions"/>.</param>
/// <returns>The <see cref="MemoryAllocator"/>.</returns>
public static MemoryAllocator Create(MemoryAllocatorOptions options) =>
new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes);
public static MemoryAllocator Create(MemoryAllocatorOptions options)
{
UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(options.MaximumPoolSizeMegabytes);
if (options.AllocationLimitMegabytes.HasValue)
{
allocator.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024 * 1024;
allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes);
}

return allocator;
}

/// <summary>
/// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="Memory{T}"/> of length <paramref name="length"/>.
Expand All @@ -65,16 +81,35 @@ public virtual void ReleaseRetainedResources()
/// <summary>
/// Allocates a <see cref="MemoryGroup{T}"/>.
/// </summary>
/// <typeparam name="T">The type of element to allocate.</typeparam>
/// <param name="totalLength">The total length of the buffer.</param>
/// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
/// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
internal virtual MemoryGroup<T> AllocateGroup<T>(
internal MemoryGroup<T> AllocateGroup<T>(
long totalLength,
int bufferAlignment,
AllocationOptions options = AllocationOptions.None)
where T : struct
=> MemoryGroup<T>.Allocate(this, totalLength, bufferAlignment, options);
{
if (totalLength < 0)
{
throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of negative length={totalLength}.");
}

ulong totalLengthInBytes = (ulong)totalLength * (ulong)Unsafe.SizeOf<T>();
if (totalLengthInBytes > (ulong)this.MemoryGroupAllocationLimitBytes)
{
throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of length={totalLengthInBytes} that exceeded the limit {this.MemoryGroupAllocationLimitBytes}.");
}

// Cast to long is safe because we already checked that the total length is within the limit.
return this.AllocateGroupCore<T>(totalLength, (long)totalLengthInBytes, bufferAlignment, options);
}

internal virtual MemoryGroup<T> AllocateGroupCore<T>(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options)
where T : struct
=> MemoryGroup<T>.Allocate(this, totalLengthInElements, bufferAlignment, options);
}
}
21 changes: 20 additions & 1 deletion src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

namespace SixLabors.ImageSharp.Memory
Expand All @@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.Memory
public struct MemoryAllocatorOptions
{
private int? maximumPoolSizeMegabytes;
private int? allocationLimitMegabytes;

/// <summary>
/// Gets or sets a value defining the maximum size of the <see cref="MemoryAllocator"/>'s internal memory pool
Expand All @@ -27,5 +28,23 @@ public int? MaximumPoolSizeMegabytes
this.maximumPoolSizeMegabytes = value;
}
}

/// <summary>
/// Gets or sets a value defining the maximum (discontiguous) buffer size that can be allocated by the allocator in Megabytes.
/// <see langword="null"/> means platform default: 1GB on 32-bit processes, 4GB on 64-bit processes.
/// </summary>
public int? AllocationLimitMegabytes
{
readonly get => this.allocationLimitMegabytes;
set
{
if (value.HasValue)
{
Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AllocationLimitMegabytes));
}

this.allocationLimitMegabytes = value;
}
}
}
}
14 changes: 12 additions & 2 deletions src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.

using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory.Internals;

namespace SixLabors.ImageSharp.Memory
Expand All @@ -17,7 +18,16 @@ public sealed class SimpleGcMemoryAllocator : MemoryAllocator
/// <inheritdoc />
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
if (length < 0)
{
throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of negative length={length}.");
}

ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf<T>();
if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes)
{
throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of length={lengthInBytes} that exceeded the limit {this.SingleBufferAllocationLimitBytes}.");
}

return new BasicArrayBuffer<T>(new T[length]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,18 @@
int length,
AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
int lengthInBytes = length * Unsafe.SizeOf<T>();
if (length < 0)
{
throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of negative length={length}.");
}

ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf<T>();
if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes)
{
throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of length={lengthInBytes} that exceeded the limit {this.SingleBufferAllocationLimitBytes}.");
}

if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes)
if (lengthInBytes <= (ulong)this.sharedArrayPoolThresholdInBytes)
{
var buffer = new SharedArrayPoolBuffer<T>(length);
if (options.Has(AllocationOptions.Clean))
Expand All @@ -101,7 +109,7 @@
return buffer;
}

if (lengthInBytes <= this.poolBufferSizeInBytes)
if (lengthInBytes <= (ulong)this.poolBufferSizeInBytes)
{
UnmanagedMemoryHandle mem = this.pool.Rent();
if (mem.IsValid)
Expand All @@ -115,20 +123,15 @@
}

/// <inheritdoc />
internal override MemoryGroup<T> AllocateGroup<T>(
long totalLength,
internal override MemoryGroup<T> AllocateGroupCore<T>(
long totalLengthInElements,
long totalLengthInBytes,
int bufferAlignment,
AllocationOptions options = AllocationOptions.None)
{
long totalLengthInBytes = totalLength * Unsafe.SizeOf<T>();
if (totalLengthInBytes < 0)
{
throw new InvalidMemoryOperationException("Attempted to allocate a MemoryGroup of a size that is not representable.");
}

if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes)
{
var buffer = new SharedArrayPoolBuffer<T>((int)totalLength);
var buffer = new SharedArrayPoolBuffer<T>((int)totalLengthInElements);
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
}

Expand All @@ -138,18 +141,18 @@
UnmanagedMemoryHandle mem = this.pool.Rent();
if (mem.IsValid)
{
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, (int)totalLength, options.Has(AllocationOptions.Clean));
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, (int)totalLengthInElements, options.Has(AllocationOptions.Clean));
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
}
}

// Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails:
if (MemoryGroup<T>.TryAllocate(this.pool, totalLength, bufferAlignment, options, out MemoryGroup<T> poolGroup))
if (MemoryGroup<T>.TryAllocate(this.pool, totalLengthInElements, bufferAlignment, options, out MemoryGroup<T>? poolGroup))

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, net6.0, 6.0.x, true, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, netcoreapp3.1, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, net5.0, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest, net5.0, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, netcoreapp2.1, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net5.0, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, netcoreapp3.1, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net6.0, 6.0.x, true, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest, net6.0, 6.0.x, true, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net472, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net472, -x86, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 150 in src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest, netcoreapp3.1, -x64, false)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
return poolGroup;
}

return MemoryGroup<T>.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options);
return MemoryGroup<T>.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options);
}

public override void ReleaseRetainedResources() => this.pool.Release();
Expand Down
14 changes: 8 additions & 6 deletions src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
/// <typeparam name="T">The element type.</typeparam>
internal abstract partial class MemoryGroup<T> : IMemoryGroup<T>, IDisposable
where T : struct
{
{

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, net6.0, 6.0.x, true, -x64, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, netcoreapp3.1, -x64, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest, net5.0, -x64, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest, net5.0, -x64, false)

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, netcoreapp2.1, -x64, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net5.0, -x64, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, netcoreapp3.1, -x64, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net6.0, 6.0.x, true, -x64, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest, net6.0, 6.0.x, true, -x64, false)

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net472, -x64, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest, net472, -x86, false)

Code should not contain trailing whitespace

Check warning on line 23 in src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

View workflow job for this annotation

GitHub Actions / Build (macos-latest, netcoreapp3.1, -x64, false)

private static readonly int ElementSize = Unsafe.SizeOf<T>();

private MemoryGroupSpanCache memoryGroupSpanCache;
Expand All @@ -43,7 +43,7 @@
/// <inheritdoc />
public bool IsValid { get; private set; } = true;

public MemoryGroupView<T> View { get; private set; }
public MemoryGroupView<T> View { get; private set; } = null!;

/// <inheritdoc />
public abstract Memory<T> this[int index] { get; }
Expand Down Expand Up @@ -85,12 +85,14 @@
{
int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes();
Guard.NotNull(allocator, nameof(allocator));
Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements));
Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements));

int blockCapacityInElements = bufferCapacityInBytes / ElementSize;
if (totalLengthInElements < 0)
{
throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of negative length={totalLengthInElements}.");
}

if (bufferAlignmentInElements > blockCapacityInElements)
int blockCapacityInElements = bufferCapacityInBytes / ElementSize;
if (bufferAlignmentInElements < 0 || bufferAlignmentInElements > blockCapacityInElements)
{
throw new InvalidMemoryOperationException(
$"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignmentInElements}.");
Expand Down
1 change: 1 addition & 0 deletions src/ImageSharp/Memory/InvalidMemoryOperationException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Diagnostics.CodeAnalysis;

namespace SixLabors.ImageSharp.Memory
{
Expand Down
13 changes: 13 additions & 0 deletions tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -619,5 +619,18 @@ public void BmpDecoder_CanDecode_Os2BitmapArray<TPixel>(TestImageProvider<TPixel
// image.CompareToOriginal(provider);
}
}

[Theory]
[WithFile(Issue2696, PixelTypes.Rgba32)]
public void BmpDecoder_ThrowsException_Issue2696<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// On V2 this is throwing InvalidOperationException,
// because of the validation logic in BmpInfoHeader.VerifyDimensions().
Assert.Throws<InvalidOperationException>(() =>
{
using Image<TPixel> image = provider.GetImage(BmpDecoder);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ public BufferTests()

protected SimpleGcMemoryAllocator MemoryAllocator { get; } = new SimpleGcMemoryAllocator();

[Theory]
[InlineData(-1)]
public void Allocate_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)
public static TheoryData<int> InvalidLengths { get; set; } = new()
{
ArgumentOutOfRangeException ex = Assert.Throws<ArgumentOutOfRangeException>(() => this.MemoryAllocator.Allocate<BigStruct>(length));
Assert.Equal("length", ex.ParamName);
}
{ -1 },
{ (1 << 30) + 1 }
};

[Theory]
[MemberData(nameof(InvalidLengths))]
public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException(int length)
=> Assert.Throws<InvalidMemoryOperationException>(
() => this.MemoryAllocator.Allocate<BigStruct>(length));

[Fact]
public unsafe void Allocate_MemoryIsPinnableMultipleTimes()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ public void AllocateGroup_SizeInBytesOverLongMaxValue_ThrowsInvalidMemoryOperati
Assert.Throws<InvalidMemoryOperationException>(() => allocator.AllocateGroup<S4>(int.MaxValue * (long)int.MaxValue, int.MaxValue));
}

public static TheoryData<int> InvalidLengths { get; set; } = new()
{
{ -1 },
{ (1 << 30) + 1 }
};

[Theory]
[MemberData(nameof(InvalidLengths))]
public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException(int length)
=> Assert.Throws<InvalidMemoryOperationException>(() => new UniformUnmanagedMemoryPoolMemoryAllocator(null).Allocate<S512>(length));

[Fact]
public unsafe void Allocate_MemoryIsPinnableMultipleTimes()
{
Expand Down Expand Up @@ -387,6 +398,30 @@ private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllo
}
}

[Fact]
public void Allocate_OverLimit_ThrowsInvalidMemoryOperationException()
{
MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions()
{
AllocationLimitMegabytes = 4
});
const int oneMb = 1 << 20;
allocator.Allocate<byte>(4 * oneMb).Dispose(); // Should work
Assert.Throws<InvalidMemoryOperationException>(() => allocator.Allocate<byte>(5 * oneMb));
}

[Fact]
public void AllocateGroup_OverLimit_ThrowsInvalidMemoryOperationException()
{
MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions()
{
AllocationLimitMegabytes = 4
});
const int oneMb = 1 << 20;
allocator.AllocateGroup<byte>(4 * oneMb, 1024).Dispose(); // Should work
Assert.Throws<InvalidMemoryOperationException>(() => allocator.AllocateGroup<byte>(5 * oneMb, 1024));
}

#if NETCOREAPP3_1_OR_GREATER
[Fact]
public void Issue2001_NegativeMemoryReportedByGc()
Expand Down
Loading
Loading