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}.");
}