Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Buffers;

namespace SixLabors.ImageSharp.Memory;

/// <summary>
/// Provides the tracked memory-owner contract required by <see cref="MemoryAllocator"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <remarks>
/// Custom allocators implement <see cref="MemoryAllocator.AllocateCore{T}(int, AllocationOptions)"/>
/// and return a derived type. The base allocator attaches allocation tracking after the owner has been
/// created so custom implementations cannot forget, duplicate, or mismatch the reservation lifecycle.
/// </remarks>
public abstract class AllocationTrackedMemoryManager<T> : MemoryManager<T>
where T : struct
{
private AllocationTrackingState allocationTracking;

/// <summary>
/// Releases resources held by the concrete tracked owner.
/// </summary>
/// <param name="disposing">
/// <see langword="true"/> when the owner is being disposed deterministically;
/// otherwise, <see langword="false"/>.
/// </param>
/// <remarks>
/// Implementations release their own resources here. Allocation tracking is released by the sealed base
/// dispose path after this method returns.
/// </remarks>
protected abstract void DisposeCore(bool disposing);

/// <inheritdoc />
protected sealed override void Dispose(bool disposing)
{
this.DisposeCore(disposing);
this.ReleaseAllocationTracking();
}

/// <summary>
/// Attaches allocation tracking to this owner after allocation has succeeded.
/// </summary>
/// <param name="allocator">The allocator that owns the reservation for this instance.</param>
/// <param name="lengthInBytes">The reserved allocation size, in bytes.</param>
/// <remarks>
/// <see cref="MemoryAllocator"/> calls this exactly once after <c>AllocateCore</c> returns.
/// Derived allocators should not call it themselves; they only construct the concrete owner.
/// </remarks>
internal void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes)
=> this.allocationTracking.Attach(allocator, lengthInBytes);

/// <summary>
/// Releases any tracked allocation bytes associated with this instance.
/// </summary>
/// <remarks>
/// Calling this more than once is safe; only the first call after tracking has been attached releases bytes.
/// </remarks>
private void ReleaseAllocationTracking() => this.allocationTracking.Release();
}
41 changes: 41 additions & 0 deletions src/ImageSharp/Memory/AllocationTrackingState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Memory;

/// <summary>
/// Tracks a single allocator reservation and releases it exactly once.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
internal struct AllocationTrackingState
{
private MemoryAllocator? allocator;
private long lengthInBytes;
private int released;

/// <summary>
/// Attaches allocator reservation tracking to the current owner.
/// </summary>
/// <param name="allocator">The allocator that owns the reservation.</param>
/// <param name="lengthInBytes">The reserved allocation size, in bytes.</param>
internal void Attach(MemoryAllocator allocator, long lengthInBytes)
{
this.allocator = allocator;
this.lengthInBytes = lengthInBytes;
}

/// <summary>
/// Releases the attached allocator reservation once.
/// </summary>
internal void Release()
{
if (Interlocked.Exchange(ref this.released, 1) == 0 && this.allocator != null)
{
this.allocator.ReleaseAccumulatedBytes(this.lengthInBytes);
this.allocator = null;
}
}
}
14 changes: 12 additions & 2 deletions src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

namespace SixLabors.ImageSharp.Memory;

/// <summary>
/// Provides helper methods for working with <see cref="AllocationOptions"/>.
/// </summary>
internal static class AllocationOptionsExtensions
{
public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag;
/// <summary>
/// Returns a value indicating whether the specified flag is set on the allocation options.
/// </summary>
/// <param name="options">The allocation options to inspect.</param>
/// <param name="flag">The flag to test for.</param>
/// <returns><see langword="true"/> if <paramref name="flag"/> is set; otherwise, <see langword="false"/>.</returns>
public static bool Has(this AllocationOptions options, AllocationOptions flag)
=> (options & flag) == flag;
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public BasicArrayBuffer(T[] array)
public override Span<T> GetSpan() => this.Array.AsSpan(0, this.Length);

/// <inheritdoc />
protected override void Dispose(bool disposing)
protected override void DisposeCore(bool disposing)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Memory.Internals;
/// Provides a base class for <see cref="IMemoryOwner{T}"/> implementations by implementing pinning logic for <see cref="MemoryManager{T}"/> adaption.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
internal abstract class ManagedBufferBase<T> : MemoryManager<T>
internal abstract class ManagedBufferBase<T> : AllocationTrackedMemoryManager<T>
where T : struct
{
private GCHandle pinHandle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class SharedArrayPoolBuffer<T> : ManagedBufferBase<T>, IRefCounted
where T : struct
{
private readonly int lengthInBytes;
private LifetimeGuard lifetimeGuard;
private readonly LifetimeGuard lifetimeGuard;

public SharedArrayPoolBuffer(int lengthInElements)
{
Expand All @@ -24,7 +24,7 @@ public SharedArrayPoolBuffer(int lengthInElements)

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

protected override void Dispose(bool disposing)
protected override void DisposeCore(bool disposing)
{
if (this.Array == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory.Internals;
/// access to unmanaged buffers allocated by <see cref="Marshal.AllocHGlobal(int)"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
internal sealed unsafe class UnmanagedBuffer<T> : MemoryManager<T>, IRefCounted
internal sealed unsafe class UnmanagedBuffer<T> : AllocationTrackedMemoryManager<T>, IRefCounted
where T : struct
{
private readonly int lengthInElements;
Expand Down Expand Up @@ -52,7 +52,7 @@ public override MemoryHandle Pin(int elementIndex = 0)
}

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

Expand Down
Loading
Loading