Skip to content

Commit 3495b01

Browse files
Merge branch 'release/3.1.x' into port/v3
2 parents 8546c74 + da5f09a commit 3495b01

21 files changed

+234
-59
lines changed

src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ public override void PrepareForDecoding()
201201
this.pixelBuffer = allocator.Allocate2D<TPixel>(
202202
pixelSize.Width,
203203
pixelSize.Height,
204-
this.Configuration.PreferContiguousImageBuffers);
204+
this.Configuration.PreferContiguousImageBuffers,
205+
AllocationOptions.Clean);
205206
this.paddedProxyPixelRow = allocator.Allocate<TPixel>(pixelSize.Width + 3);
206207

207208
// Component processors from spectral to RGB

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1983,6 +1983,9 @@ private IMemoryOwner<byte> ReadChunkData(int length)
19831983
}
19841984

19851985
// We rent the buffer here to return it afterwards in Decode()
1986+
// We don't want to throw a degenerated memory exception here as we want to allow partial decoding
1987+
// so limit the length.
1988+
length = (int)Math.Min(length, this.currentStream.Length - this.currentStream.Position);
19861989
IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
19871990

19881991
this.currentStream.Read(buffer.GetSpan(), 0, length);

src/ImageSharp/Formats/Png/PngScanlineProcessor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,12 @@ public static void ProcessInterlacedPaletteScanline<TPixel>(
181181
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan);
182182
ref Color paletteBase = ref MemoryMarshal.GetReference(palette.Value.Span);
183183
uint offset = pixelOffset + frameControl.XOffset;
184+
int maxIndex = palette.Value.Length - 1;
184185

185186
for (nuint x = offset, o = 0; x < frameControl.XMax; x += increment, o++)
186187
{
187188
uint index = Unsafe.Add(ref scanlineSpanRef, o);
188-
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(Unsafe.Add(ref paletteBase, index).ToPixel<Rgba32>());
189+
Unsafe.Add(ref rowSpanRef, x) = TPixel.FromRgba32(Unsafe.Add(ref paletteBase, (int)Math.Min(index, maxIndex)).ToPixel<Rgba32>());
189190
}
190191
}
191192

src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken
8484
throw new UnknownImageFormatException("Width or height cannot be 0");
8585
}
8686

87-
Image<TPixel> image = Image.CreateUninitialized<TPixel>(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
87+
Image<TPixel> image = new(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
8888
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
8989

9090
if (this.fileHeader.ColorMapType == 1)

src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Memory.Internals;
1111
internal struct UnmanagedMemoryHandle : IEquatable<UnmanagedMemoryHandle>
1212
{
1313
// Number of allocation re-attempts when detecting OutOfMemoryException.
14-
private const int MaxAllocationAttempts = 1000;
14+
private const int MaxAllocationAttempts = 10;
1515

1616
// Track allocations for testing purposes:
1717
private static int totalOutstandingHandles;

src/ImageSharp/Memory/Allocators/MemoryAllocator.cs

Lines changed: 40 additions & 6 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.Runtime.CompilerServices;
56

67
namespace SixLabors.ImageSharp.Memory;
78

@@ -10,6 +11,8 @@ namespace SixLabors.ImageSharp.Memory;
1011
/// </summary>
1112
public abstract class MemoryAllocator
1213
{
14+
private const int OneGigabyte = 1 << 30;
15+
1316
/// <summary>
1417
/// Gets the default platform-specific global <see cref="MemoryAllocator"/> instance that
1518
/// serves as the default value for <see cref="Configuration.MemoryAllocator"/>.
@@ -20,6 +23,10 @@ public abstract class MemoryAllocator
2023
/// </summary>
2124
public static MemoryAllocator Default { get; } = Create();
2225

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

3642
/// <summary>
3743
/// Creates the default <see cref="MemoryAllocator"/> using the provided options.
3844
/// </summary>
3945
/// <param name="options">The <see cref="MemoryAllocatorOptions"/>.</param>
4046
/// <returns>The <see cref="MemoryAllocator"/>.</returns>
41-
public static MemoryAllocator Create(MemoryAllocatorOptions options) =>
42-
new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes);
47+
public static MemoryAllocator Create(MemoryAllocatorOptions options)
48+
{
49+
UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(options.MaximumPoolSizeMegabytes);
50+
if (options.AllocationLimitMegabytes.HasValue)
51+
{
52+
allocator.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024 * 1024;
53+
allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes);
54+
}
55+
56+
return allocator;
57+
}
4358

4459
/// <summary>
4560
/// Allocates an <see cref="IMemoryOwner{T}" />, holding a <see cref="Memory{T}"/> of length <paramref name="length"/>.
@@ -64,15 +79,34 @@ public virtual void ReleaseRetainedResources()
6479
/// <summary>
6580
/// Allocates a <see cref="MemoryGroup{T}"/>.
6681
/// </summary>
82+
/// <typeparam name="T">The type of element to allocate.</typeparam>
6783
/// <param name="totalLength">The total length of the buffer.</param>
6884
/// <param name="bufferAlignment">The expected alignment (eg. to make sure image rows fit into single buffers).</param>
6985
/// <param name="options">The <see cref="AllocationOptions"/>.</param>
7086
/// <returns>A new <see cref="MemoryGroup{T}"/>.</returns>
7187
/// <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>(
88+
internal MemoryGroup<T> AllocateGroup<T>(
7389
long totalLength,
7490
int bufferAlignment,
7591
AllocationOptions options = AllocationOptions.None)
7692
where T : struct
77-
=> MemoryGroup<T>.Allocate(this, totalLength, bufferAlignment, options);
93+
{
94+
if (totalLength < 0)
95+
{
96+
InvalidMemoryOperationException.ThrowNegativeAllocationException(totalLength);
97+
}
98+
99+
ulong totalLengthInBytes = (ulong)totalLength * (ulong)Unsafe.SizeOf<T>();
100+
if (totalLengthInBytes > (ulong)this.MemoryGroupAllocationLimitBytes)
101+
{
102+
InvalidMemoryOperationException.ThrowAllocationOverLimitException(totalLengthInBytes, this.MemoryGroupAllocationLimitBytes);
103+
}
104+
105+
// Cast to long is safe because we already checked that the total length is within the limit.
106+
return this.AllocateGroupCore<T>(totalLength, (long)totalLengthInBytes, bufferAlignment, options);
107+
}
108+
109+
internal virtual MemoryGroup<T> AllocateGroupCore<T>(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options)
110+
where T : struct
111+
=> MemoryGroup<T>.Allocate(this, totalLengthInElements, bufferAlignment, options);
78112
}

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: 12 additions & 2 deletions
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;
@@ -17,7 +18,16 @@ public sealed class SimpleGcMemoryAllocator : MemoryAllocator
1718
/// <inheritdoc />
1819
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
1920
{
20-
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
21+
if (length < 0)
22+
{
23+
InvalidMemoryOperationException.ThrowNegativeAllocationException(length);
24+
}
25+
26+
ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf<T>();
27+
if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes)
28+
{
29+
InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes);
30+
}
2131

2232
return new BasicArrayBuffer<T>(new T[length]);
2333
}

src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,18 @@ public override IMemoryOwner<T> Allocate<T>(
8383
int length,
8484
AllocationOptions options = AllocationOptions.None)
8585
{
86-
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
87-
int lengthInBytes = length * Unsafe.SizeOf<T>();
86+
if (length < 0)
87+
{
88+
InvalidMemoryOperationException.ThrowNegativeAllocationException(length);
89+
}
90+
91+
ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf<T>();
92+
if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes)
93+
{
94+
InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes);
95+
}
8896

89-
if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes)
97+
if (lengthInBytes <= (ulong)this.sharedArrayPoolThresholdInBytes)
9098
{
9199
var buffer = new SharedArrayPoolBuffer<T>(length);
92100
if (options.Has(AllocationOptions.Clean))
@@ -97,7 +105,7 @@ public override IMemoryOwner<T> Allocate<T>(
97105
return buffer;
98106
}
99107

100-
if (lengthInBytes <= this.poolBufferSizeInBytes)
108+
if (lengthInBytes <= (ulong)this.poolBufferSizeInBytes)
101109
{
102110
UnmanagedMemoryHandle mem = this.pool.Rent();
103111
if (mem.IsValid)
@@ -111,20 +119,15 @@ public override IMemoryOwner<T> Allocate<T>(
111119
}
112120

113121
/// <inheritdoc />
114-
internal override MemoryGroup<T> AllocateGroup<T>(
115-
long totalLength,
122+
internal override MemoryGroup<T> AllocateGroupCore<T>(
123+
long totalLengthInElements,
124+
long totalLengthInBytes,
116125
int bufferAlignment,
117126
AllocationOptions options = AllocationOptions.None)
118127
{
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-
125128
if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes)
126129
{
127-
var buffer = new SharedArrayPoolBuffer<T>((int)totalLength);
130+
var buffer = new SharedArrayPoolBuffer<T>((int)totalLengthInElements);
128131
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
129132
}
130133

@@ -134,18 +137,18 @@ internal override MemoryGroup<T> AllocateGroup<T>(
134137
UnmanagedMemoryHandle mem = this.pool.Rent();
135138
if (mem.IsValid)
136139
{
137-
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, (int)totalLength, options.Has(AllocationOptions.Clean));
140+
UnmanagedBuffer<T> buffer = this.pool.CreateGuardedBuffer<T>(mem, (int)totalLengthInElements, options.Has(AllocationOptions.Clean));
138141
return MemoryGroup<T>.CreateContiguous(buffer, options.Has(AllocationOptions.Clean));
139142
}
140143
}
141144

142145
// 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))
146+
if (MemoryGroup<T>.TryAllocate(this.pool, totalLengthInElements, bufferAlignment, options, out MemoryGroup<T>? poolGroup))
144147
{
145148
return poolGroup;
146149
}
147150

148-
return MemoryGroup<T>.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options);
151+
return MemoryGroup<T>.Allocate(this.nonPoolAllocator, totalLengthInElements, bufferAlignment, options);
149152
}
150153

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

src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,16 @@ public static MemoryGroup<T> Allocate(
8383
{
8484
int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes();
8585
Guard.NotNull(allocator, nameof(allocator));
86-
Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements));
87-
Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements));
8886

89-
int blockCapacityInElements = bufferCapacityInBytes / ElementSize;
87+
if (totalLengthInElements < 0)
88+
{
89+
InvalidMemoryOperationException.ThrowNegativeAllocationException(totalLengthInElements);
90+
}
9091

91-
if (bufferAlignmentInElements > blockCapacityInElements)
92+
int blockCapacityInElements = bufferCapacityInBytes / ElementSize;
93+
if (bufferAlignmentInElements < 0 || bufferAlignmentInElements > blockCapacityInElements)
9294
{
93-
throw new InvalidMemoryOperationException(
94-
$"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignmentInElements}.");
95+
InvalidMemoryOperationException.ThrowInvalidAlignmentException(bufferAlignmentInElements);
9596
}
9697

9798
if (totalLengthInElements == 0)

0 commit comments

Comments
 (0)