Skip to content

Commit

Permalink
Merge pull request #2715 from SixLabors/backport/v2-memlimit
Browse files Browse the repository at this point in the history
V2 - Limit all memory allocations in the MemoryAllocator layer
  • Loading branch information
antonfirsov authored Apr 10, 2024
2 parents 8f0b4d3 + cf9496d commit f21d641
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 43 deletions.
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 @@ public override IMemoryOwner<T> Allocate<T>(
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 @@ public override IMemoryOwner<T> Allocate<T>(
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 @@ public override IMemoryOwner<T> Allocate<T>(
}

/// <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 @@ internal override MemoryGroup<T> AllocateGroup<T>(
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, 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 (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 (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, 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 (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, 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, 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, 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 (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 (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 (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 (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, 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, 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, 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 (macos-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 (macos-latest, net5.0, -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 @@ namespace SixLabors.ImageSharp.Memory
/// <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, 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 (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 (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, 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 (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, 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, 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 (macos-latest, netcoreapp3.1, -x64, false)

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 (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 (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 (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, 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 (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, 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, 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 (macos-latest, netcoreapp3.1, -x64, false)

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)

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

private MemoryGroupSpanCache memoryGroupSpanCache;
Expand All @@ -43,7 +43,7 @@ private MemoryGroup(int bufferLength, long totalLength)
/// <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 @@ public static MemoryGroup<T> Allocate(
{
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

0 comments on commit f21d641

Please sign in to comment.