From 92b82779ac8e7a032989533bb9a01ef092186b2e Mon Sep 17 00:00:00 2001 From: antonfirsov Date: Thu, 28 Mar 2024 02:36:45 +0100 Subject: [PATCH] Limit all allocations --- .../Memory/Allocators/MemoryAllocator.cs | 51 ++++++++++++++++--- .../Allocators/MemoryAllocatorOptions.cs | 21 +++++++- .../Allocators/SimpleGcMemoryAllocator.cs | 10 +++- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 25 ++++----- .../Memory/InvalidMemoryOperationException.cs | 6 +++ 5 files changed, 93 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 2bd9cb5eef..194449cfc1 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -2,6 +2,8 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Memory; @@ -10,6 +12,8 @@ namespace SixLabors.ImageSharp.Memory; /// public abstract class MemoryAllocator { + private const int OneGigabyte = 1 << 30; + /// /// Gets the default platform-specific global instance that /// serves as the default value for . @@ -20,6 +24,12 @@ public abstract class MemoryAllocator /// public static MemoryAllocator Default { get; } = Create(); + internal long MemoryGroupAllocationLimitBytes { get; private set; } = Environment.Is64BitProcess ? + 4L * OneGigabyte : + OneGigabyte; + + internal int SingleBufferAllocationLimitBytes { get; private set; } = OneGigabyte; + /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// @@ -30,16 +40,24 @@ public abstract class MemoryAllocator /// Creates a default instance of a optimized for the executing platform. /// /// The . - public static MemoryAllocator Create() => - new UniformUnmanagedMemoryPoolMemoryAllocator(null); + public static MemoryAllocator Create() => Create(default); /// /// Creates the default using the provided options. /// /// The . /// The . - 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; + } /// /// Allocates an , holding a of length . @@ -69,10 +87,31 @@ public virtual void ReleaseRetainedResources() /// The . /// A new . /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. - internal virtual MemoryGroup AllocateGroup( + internal MemoryGroup AllocateGroup( long totalLength, int bufferAlignment, AllocationOptions options = AllocationOptions.None) where T : struct - => MemoryGroup.Allocate(this, totalLength, bufferAlignment, options); + { + long totalLengthInBytes = totalLength * Unsafe.SizeOf(); + if (totalLengthInBytes < 0) + { + ThrowNotRepresentable(); + } + + if (totalLengthInBytes > this.MemoryGroupAllocationLimitBytes) + { + InvalidMemoryOperationException.ThrowAllocationOverLimitException(totalLengthInBytes, this.MemoryGroupAllocationLimitBytes); + } + + return this.AllocateGroupCore(totalLengthInBytes, totalLength, bufferAlignment, options); + + [DoesNotReturn] + static void ThrowNotRepresentable() => + throw new InvalidMemoryOperationException("Attempted to allocate a MemoryGroup of a size that is not representable."); + } + + internal virtual MemoryGroup AllocateGroupCore(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options) + where T : struct + => MemoryGroup.Allocate(this, totalLengthInElements, bufferAlignment, options); } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs index 5a821fd04a..d9ba62c1ef 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Memory; @@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.Memory; public struct MemoryAllocatorOptions { private int? maximumPoolSizeMegabytes; + private int? allocationLimitMegabytes; /// /// Gets or sets a value defining the maximum size of the 's internal memory pool @@ -27,4 +28,22 @@ public int? MaximumPoolSizeMegabytes this.maximumPoolSizeMegabytes = value; } } + + /// + /// Gets or sets a value defining the maximum (discontiguous) buffer size that can be allocated by the allocator in Megabytes. + /// means platform default: 1GB on 32-bit processes, 4GB on 64-bit processes. + /// + public int? AllocationLimitMegabytes + { + get => this.allocationLimitMegabytes; + set + { + if (value.HasValue) + { + Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AllocationLimitMegabytes)); + } + + this.allocationLimitMegabytes = value; + } + } } diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 41730d9678..e604621954 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Buffers; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory; @@ -19,6 +20,13 @@ public override IMemoryOwner Allocate(int length, AllocationOptions option { Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + int lengthInBytes = length * Unsafe.SizeOf(); + + if (lengthInBytes > this.SingleBufferAllocationLimitBytes) + { + InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); + } + return new BasicArrayBuffer(new T[length]); } } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 0bae193632..585d4717ac 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; @@ -86,6 +87,11 @@ public override IMemoryOwner Allocate( Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); int lengthInBytes = length * Unsafe.SizeOf(); + if (lengthInBytes > this.SingleBufferAllocationLimitBytes) + { + InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); + } + if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes) { var buffer = new SharedArrayPoolBuffer(length); @@ -111,20 +117,15 @@ public override IMemoryOwner Allocate( } /// - internal override MemoryGroup AllocateGroup( - long totalLength, + internal override MemoryGroup AllocateGroupCore( + long totalLengthInElements, + long totalLengthInBytes, int bufferAlignment, AllocationOptions options = AllocationOptions.None) { - long totalLengthInBytes = totalLength * Unsafe.SizeOf(); - 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((int)totalLength); + var buffer = new SharedArrayPoolBuffer((int)totalLengthInElements); return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); } @@ -134,18 +135,18 @@ internal override MemoryGroup AllocateGroup( UnmanagedMemoryHandle mem = this.pool.Rent(); if (mem.IsValid) { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLength, options.Has(AllocationOptions.Clean)); + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLengthInElements, options.Has(AllocationOptions.Clean)); return MemoryGroup.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.TryAllocate(this.pool, totalLength, bufferAlignment, options, out MemoryGroup? poolGroup)) + if (MemoryGroup.TryAllocate(this.pool, totalLengthInElements, bufferAlignment, options, out MemoryGroup? poolGroup)) { return poolGroup; } - return MemoryGroup.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options); + return MemoryGroup.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options); } public override void ReleaseRetainedResources() => this.pool.Release(); diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs index 6a55472236..0dc8106b6b 100644 --- a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.CodeAnalysis; + namespace SixLabors.ImageSharp.Memory; /// @@ -24,4 +26,8 @@ public InvalidMemoryOperationException(string message) public InvalidMemoryOperationException() { } + + [DoesNotReturn] + internal static void ThrowAllocationOverLimitException(long length, long limit) => + throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of length={length} that exceeded the limit {limit}."); }