Skip to content

Commit 92b8277

Browse files
committed
Limit all allocations
1 parent a29c6fd commit 92b8277

File tree

5 files changed

+93
-20
lines changed

5 files changed

+93
-20
lines changed

src/ImageSharp/Memory/Allocators/MemoryAllocator.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Runtime.CompilerServices;
57

68
namespace SixLabors.ImageSharp.Memory;
79

@@ -10,6 +12,8 @@ namespace SixLabors.ImageSharp.Memory;
1012
/// </summary>
1113
public abstract class MemoryAllocator
1214
{
15+
private const int OneGigabyte = 1 << 30;
16+
1317
/// <summary>
1418
/// Gets the default platform-specific global <see cref="MemoryAllocator"/> instance that
1519
/// serves as the default value for <see cref="Configuration.MemoryAllocator"/>.
@@ -20,6 +24,12 @@ public abstract class MemoryAllocator
2024
/// </summary>
2125
public static MemoryAllocator Default { get; } = Create();
2226

27+
internal long MemoryGroupAllocationLimitBytes { get; private set; } = Environment.Is64BitProcess ?
28+
4L * OneGigabyte :
29+
OneGigabyte;
30+
31+
internal int SingleBufferAllocationLimitBytes { get; private set; } = OneGigabyte;
32+
2333
/// <summary>
2434
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes.
2535
/// </summary>
@@ -30,16 +40,24 @@ public abstract class MemoryAllocator
3040
/// Creates a default instance of a <see cref="MemoryAllocator"/> optimized for the executing platform.
3141
/// </summary>
3242
/// <returns>The <see cref="MemoryAllocator"/>.</returns>
33-
public static MemoryAllocator Create() =>
34-
new UniformUnmanagedMemoryPoolMemoryAllocator(null);
43+
public static MemoryAllocator Create() => Create(default);
3544

3645
/// <summary>
3746
/// Creates the default <see cref="MemoryAllocator"/> using the provided options.
3847
/// </summary>
3948
/// <param name="options">The <see cref="MemoryAllocatorOptions"/>.</param>
4049
/// <returns>The <see cref="MemoryAllocator"/>.</returns>
41-
public static MemoryAllocator Create(MemoryAllocatorOptions options) =>
42-
new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes);
50+
public static MemoryAllocator Create(MemoryAllocatorOptions options)
51+
{
52+
UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(options.MaximumPoolSizeMegabytes);
53+
if (options.AllocationLimitMegabytes.HasValue)
54+
{
55+
allocator.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024 * 1024;
56+
allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes);
57+
}
58+
59+
return allocator;
60+
}
4361

4462
/// <summary>
4563
/// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="Memory{T}"/> of length <paramref name="length"/>.
@@ -69,10 +87,31 @@ public virtual void ReleaseRetainedResources()
6987
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
7088
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
7189
/// <exception cref="InvalidMemoryOperationException">Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator.</exception>
72-
internal virtual MemoryGroup<T> AllocateGroup<T>(
90+
internal MemoryGroup<T> AllocateGroup<T>(
7391
long totalLength,
7492
int bufferAlignment,
7593
AllocationOptions options = AllocationOptions.None)
7694
where T : struct
77-
=> MemoryGroup<T>.Allocate(this, totalLength, bufferAlignment, options);
95+
{
96+
long totalLengthInBytes = totalLength * Unsafe.SizeOf<T>();
97+
if (totalLengthInBytes < 0)
98+
{
99+
ThrowNotRepresentable();
100+
}
101+
102+
if (totalLengthInBytes > this.MemoryGroupAllocationLimitBytes)
103+
{
104+
InvalidMemoryOperationException.ThrowAllocationOverLimitException(totalLengthInBytes, this.MemoryGroupAllocationLimitBytes);
105+
}
106+
107+
return this.AllocateGroupCore<T>(totalLengthInBytes, totalLength, bufferAlignment, options);
108+
109+
[DoesNotReturn]
110+
static void ThrowNotRepresentable() =>
111+
throw new InvalidMemoryOperationException("Attempted to allocate a MemoryGroup of a size that is not representable.");
112+
}
113+
114+
internal virtual MemoryGroup<T> AllocateGroupCore<T>(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options)
115+
where T : struct
116+
=> MemoryGroup<T>.Allocate(this, totalLengthInElements, bufferAlignment, options);
78117
}

src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
namespace SixLabors.ImageSharp.Memory;
@@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.Memory;
99
public struct MemoryAllocatorOptions
1010
{
1111
private int? maximumPoolSizeMegabytes;
12+
private int? allocationLimitMegabytes;
1213

1314
/// <summary>
1415
/// Gets or sets a value defining the maximum size of the <see cref="MemoryAllocator"/>'s internal memory pool
@@ -27,4 +28,22 @@ public int? MaximumPoolSizeMegabytes
2728
this.maximumPoolSizeMegabytes = value;
2829
}
2930
}
31+
32+
/// <summary>
33+
/// Gets or sets a value defining the maximum (discontiguous) buffer size that can be allocated by the allocator in Megabytes.
34+
/// <see langword="null"/> means platform default: 1GB on 32-bit processes, 4GB on 64-bit processes.
35+
/// </summary>
36+
public int? AllocationLimitMegabytes
37+
{
38+
get => this.allocationLimitMegabytes;
39+
set
40+
{
41+
if (value.HasValue)
42+
{
43+
Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AllocationLimitMegabytes));
44+
}
45+
46+
this.allocationLimitMegabytes = value;
47+
}
48+
}
3049
}

src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers;
5+
using System.Runtime.CompilerServices;
56
using SixLabors.ImageSharp.Memory.Internals;
67

78
namespace SixLabors.ImageSharp.Memory;
@@ -19,6 +20,13 @@ public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions option
1920
{
2021
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
2122

23+
int lengthInBytes = length * Unsafe.SizeOf<T>();
24+
25+
if (lengthInBytes > this.SingleBufferAllocationLimitBytes)
26+
{
27+
InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes);
28+
}
29+
2230
return new BasicArrayBuffer<T>(new T[length]);
2331
}
2432
}

src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Six Labors Split License.
33

44
using System.Buffers;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Runtime.CompilerServices;
67
using SixLabors.ImageSharp.Memory.Internals;
78

@@ -86,6 +87,11 @@ public override IMemoryOwner<T> Allocate<T>(
8687
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
8788
int lengthInBytes = length * Unsafe.SizeOf<T>();
8889

90+
if (lengthInBytes > this.SingleBufferAllocationLimitBytes)
91+
{
92+
InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes);
93+
}
94+
8995
if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes)
9096
{
9197
var buffer = new SharedArrayPoolBuffer<T>(length);
@@ -111,20 +117,15 @@ public override IMemoryOwner<T> Allocate<T>(
111117
}
112118

113119
/// <inheritdoc />
114-
internal override MemoryGroup<T> AllocateGroup<T>(
115-
long totalLength,
120+
internal override MemoryGroup<T> AllocateGroupCore<T>(
121+
long totalLengthInElements,
122+
long totalLengthInBytes,
116123
int bufferAlignment,
117124
AllocationOptions options = AllocationOptions.None)
118125
{
119-
long totalLengthInBytes = totalLength * Unsafe.SizeOf<T>();
120-
if (totalLengthInBytes < 0)
121-
{
122-
throw new InvalidMemoryOperationException("Attempted to allocate a MemoryGroup of a size that is not representable.");
123-
}
124-
125126
if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes)
126127
{
127-
var buffer = new SharedArrayPoolBuffer<T>((int)totalLength);
128+
var buffer = new SharedArrayPoolBuffer<T>((int)totalLengthInElements);
128129
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
129130
}
130131

@@ -134,18 +135,18 @@ internal override MemoryGroup<T> AllocateGroup<T>(
134135
UnmanagedMemoryHandle mem = this.pool.Rent();
135136
if (mem.IsValid)
136137
{
137-
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, (int)totalLength, options.Has(AllocationOptions.Clean));
138+
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, (int)totalLengthInElements, options.Has(AllocationOptions.Clean));
138139
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
139140
}
140141
}
141142

142143
// Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails:
143-
if (MemoryGroup<T>.TryAllocate(this.pool, totalLength, bufferAlignment, options, out MemoryGroup<T>? poolGroup))
144+
if (MemoryGroup<T>.TryAllocate(this.pool, totalLengthInElements, bufferAlignment, options, out MemoryGroup<T>? poolGroup))
144145
{
145146
return poolGroup;
146147
}
147148

148-
return MemoryGroup<T>.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options);
149+
return MemoryGroup<T>.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options);
149150
}
150151

151152
public override void ReleaseRetainedResources() => this.pool.Release();

src/ImageSharp/Memory/InvalidMemoryOperationException.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Diagnostics.CodeAnalysis;
5+
46
namespace SixLabors.ImageSharp.Memory;
57

68
/// <summary>
@@ -24,4 +26,8 @@ public InvalidMemoryOperationException(string message)
2426
public InvalidMemoryOperationException()
2527
{
2628
}
29+
30+
[DoesNotReturn]
31+
internal static void ThrowAllocationOverLimitException(long length, long limit) =>
32+
throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of length={length} that exceeded the limit {limit}.");
2733
}

0 commit comments

Comments
 (0)