From 631b64ea7d2e81978d75ee0a8558f350f1f8c7a9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 20 Apr 2026 00:44:23 +1000 Subject: [PATCH] Add AllocationTrackingState and refactor tracking --- .../AllocationTrackedMemoryManager{T}.cs | 18 ++------ .../Memory/AllocationTrackingState.cs | 41 +++++++++++++++++++ .../DiscontiguousBuffers/MemoryGroup{T}.cs | 20 ++------- 3 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 src/ImageSharp/Memory/AllocationTrackingState.cs diff --git a/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs b/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs index 094c4dea4d..cb71bdfd40 100644 --- a/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs +++ b/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs @@ -17,9 +17,7 @@ namespace SixLabors.ImageSharp.Memory; public abstract class AllocationTrackedMemoryManager : MemoryManager where T : struct { - private MemoryAllocator? trackingAllocator; - private long trackingLengthInBytes; - private int trackingReleased; + private AllocationTrackingState allocationTracking; /// /// Releases resources held by the concrete tracked owner. @@ -51,10 +49,7 @@ public abstract class AllocationTrackedMemoryManager : MemoryManager /// Derived allocators should not call it themselves; they only construct the concrete owner. /// internal void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) - { - this.trackingAllocator = allocator; - this.trackingLengthInBytes = lengthInBytes; - } + => this.allocationTracking.Attach(allocator, lengthInBytes); /// /// Releases any tracked allocation bytes associated with this instance. @@ -62,12 +57,5 @@ public abstract class AllocationTrackedMemoryManager : MemoryManager /// /// Calling this more than once is safe; only the first call after tracking has been attached releases bytes. /// - private void ReleaseAllocationTracking() - { - if (Interlocked.Exchange(ref this.trackingReleased, 1) == 0 && this.trackingAllocator != null) - { - this.trackingAllocator.ReleaseAccumulatedBytes(this.trackingLengthInBytes); - this.trackingAllocator = null; - } - } + private void ReleaseAllocationTracking() => this.allocationTracking.Release(); } diff --git a/src/ImageSharp/Memory/AllocationTrackingState.cs b/src/ImageSharp/Memory/AllocationTrackingState.cs new file mode 100644 index 0000000000..a0c22ccebf --- /dev/null +++ b/src/ImageSharp/Memory/AllocationTrackingState.cs @@ -0,0 +1,41 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Memory; + +/// +/// Tracks a single allocator reservation and releases it exactly once. +/// +/// +/// This type is intended to live as a mutable field on the owning object. It should not be copied +/// after tracking has been attached, because the owner relies on a single shared release state. +/// +internal struct AllocationTrackingState +{ + private MemoryAllocator? allocator; + private long lengthInBytes; + private int released; + + /// + /// Attaches allocator reservation tracking to the current owner. + /// + /// The allocator that owns the reservation. + /// The reserved allocation size, in bytes. + internal void Attach(MemoryAllocator allocator, long lengthInBytes) + { + this.allocator = allocator; + this.lengthInBytes = lengthInBytes; + } + + /// + /// Releases the attached allocator reservation once. + /// + internal void Release() + { + if (Interlocked.Exchange(ref this.released, 1) == 0 && this.allocator != null) + { + this.allocator.ReleaseAccumulatedBytes(this.lengthInBytes); + this.allocator = null; + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 2f586a74c8..870c852d50 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -21,10 +21,8 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable { private static readonly int ElementSize = Unsafe.SizeOf(); + private AllocationTrackingState allocationTracking; private MemoryGroupSpanCache memoryGroupSpanCache; - private MemoryAllocator? trackingAllocator; - private long trackingLengthInBytes; - private int trackingReleased; private MemoryGroup(int bufferLength, long totalLength) { @@ -64,11 +62,8 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable /// Intended for one-time initialization after the group has been created; callers should avoid changing /// tracking state concurrently with disposal. /// - internal void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) - { - this.trackingAllocator = allocator; - this.trackingLengthInBytes = lengthInBytes; - } + internal void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) => + this.allocationTracking.Attach(allocator, lengthInBytes); /// /// Releases any resources or tracking information associated with allocation tracking for this instance. @@ -77,14 +72,7 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable /// This method is intended to be called when allocation tracking is no longer needed. It is safe /// to call multiple times; subsequent calls after the first have no effect, even when called concurrently. /// - internal void ReleaseAllocationTracking() - { - if (Interlocked.Exchange(ref this.trackingReleased, 1) == 0 && this.trackingAllocator != null) - { - this.trackingAllocator.ReleaseAccumulatedBytes(this.trackingLengthInBytes); - this.trackingAllocator = null; - } - } + internal void ReleaseAllocationTracking() => this.allocationTracking.Release(); /// IEnumerator> IEnumerable>.GetEnumerator()