Skip to content

Commit 523aacd

Browse files
Add AllocationTrackedMemoryManager and refactor allocators
1 parent 10f749d commit 523aacd

20 files changed

Lines changed: 536 additions & 77 deletions
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Buffers;
5+
6+
namespace SixLabors.ImageSharp.Memory;
7+
8+
/// <summary>
9+
/// Provides the tracked memory-owner contract required by <see cref="MemoryAllocator"/>.
10+
/// </summary>
11+
/// <typeparam name="T">The element type.</typeparam>
12+
/// <remarks>
13+
/// Custom allocators implement <see cref="MemoryAllocator.AllocateCore{T}(int, AllocationOptions)"/>
14+
/// and return a derived type. The base allocator attaches allocation tracking after the owner has been
15+
/// created so custom implementations cannot forget, duplicate, or mismatch the reservation lifecycle.
16+
/// </remarks>
17+
public abstract class AllocationTrackedMemoryManager<T> : MemoryManager<T>
18+
where T : struct
19+
{
20+
private MemoryAllocator? trackingAllocator;
21+
private long trackingLengthInBytes;
22+
private int trackingReleased;
23+
24+
/// <summary>
25+
/// Releases resources held by the concrete tracked owner.
26+
/// </summary>
27+
/// <param name="disposing">
28+
/// <see langword="true"/> when the owner is being disposed deterministically;
29+
/// otherwise, <see langword="false"/>.
30+
/// </param>
31+
/// <remarks>
32+
/// Implementations release their own resources here. Allocation tracking is released by the sealed base
33+
/// dispose path after this method returns.
34+
/// </remarks>
35+
protected abstract void DisposeCore(bool disposing);
36+
37+
/// <inheritdoc />
38+
protected sealed override void Dispose(bool disposing)
39+
{
40+
this.DisposeCore(disposing);
41+
this.ReleaseAllocationTracking();
42+
}
43+
44+
/// <summary>
45+
/// Attaches allocation tracking to this owner after allocation has succeeded.
46+
/// </summary>
47+
/// <param name="allocator">The allocator that owns the reservation for this instance.</param>
48+
/// <param name="lengthInBytes">The reserved allocation size, in bytes.</param>
49+
/// <remarks>
50+
/// <see cref="MemoryAllocator"/> calls this exactly once after <c>AllocateCore</c> returns.
51+
/// Derived allocators should not call it themselves; they only construct the concrete owner.
52+
/// </remarks>
53+
internal void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes)
54+
{
55+
this.trackingAllocator = allocator;
56+
this.trackingLengthInBytes = lengthInBytes;
57+
}
58+
59+
/// <summary>
60+
/// Releases any tracked allocation bytes associated with this instance.
61+
/// </summary>
62+
/// <remarks>
63+
/// Calling this more than once is safe; only the first call after tracking has been attached releases bytes.
64+
/// </remarks>
65+
private void ReleaseAllocationTracking()
66+
{
67+
if (Interlocked.Exchange(ref this.trackingReleased, 1) == 0 && this.trackingAllocator != null)
68+
{
69+
this.trackingAllocator.ReleaseAccumulatedBytes(this.trackingLengthInBytes);
70+
this.trackingAllocator = null;
71+
}
72+
}
73+
}
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
namespace SixLabors.ImageSharp.Memory;
55

6+
/// <summary>
7+
/// Provides helper methods for working with <see cref="AllocationOptions"/>.
8+
/// </summary>
69
internal static class AllocationOptionsExtensions
710
{
8-
public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag;
11+
/// <summary>
12+
/// Returns a value indicating whether the specified flag is set on the allocation options.
13+
/// </summary>
14+
/// <param name="options">The allocation options to inspect.</param>
15+
/// <param name="flag">The flag to test for.</param>
16+
/// <returns><see langword="true"/> if <paramref name="flag"/> is set; otherwise, <see langword="false"/>.</returns>
17+
public static bool Has(this AllocationOptions options, AllocationOptions flag)
18+
=> (options & flag) == flag;
919
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public BasicArrayBuffer(T[] array)
4747
public override Span<T> GetSpan() => this.Array.AsSpan(0, this.Length);
4848

4949
/// <inheritdoc />
50-
protected override void Dispose(bool disposing)
50+
protected override void DisposeCore(bool disposing)
5151
{
5252
}
5353

src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.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
/// Provides a base class for <see cref="IMemoryOwner{T}"/> implementations by implementing pinning logic for <see cref="MemoryManager{T}"/> adaption.
1212
/// </summary>
1313
/// <typeparam name="T">The element type.</typeparam>
14-
internal abstract class ManagedBufferBase<T> : MemoryManager<T>
14+
internal abstract class ManagedBufferBase<T> : AllocationTrackedMemoryManager<T>
1515
where T : struct
1616
{
1717
private GCHandle pinHandle;

src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public SharedArrayPoolBuffer(int lengthInElements)
2424

2525
public byte[]? Array { get; private set; }
2626

27-
protected override void Dispose(bool disposing)
27+
protected override void DisposeCore(bool disposing)
2828
{
2929
if (this.Array == null)
3030
{

src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory.Internals;
1212
/// access to unmanaged buffers allocated by <see cref="Marshal.AllocHGlobal(int)"/>.
1313
/// </summary>
1414
/// <typeparam name="T">The element type.</typeparam>
15-
internal sealed unsafe class UnmanagedBuffer<T> : MemoryManager<T>, IRefCounted
15+
internal sealed unsafe class UnmanagedBuffer<T> : AllocationTrackedMemoryManager<T>, IRefCounted
1616
where T : struct
1717
{
1818
private readonly int lengthInElements;
@@ -52,7 +52,7 @@ public override MemoryHandle Pin(int elementIndex = 0)
5252
}
5353

5454
/// <inheritdoc />
55-
protected override void Dispose(bool disposing)
55+
protected override void DisposeCore(bool disposing)
5656
{
5757
DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!");
5858

0 commit comments

Comments
 (0)