mirror of https://github.com/SixLabors/ImageSharp
27 changed files with 988 additions and 564 deletions
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Memory.Internals |
|||
{ |
|||
/// <summary>
|
|||
/// Defines an common interface for ref-counted objects.
|
|||
/// </summary>
|
|||
internal interface IRefCounted |
|||
{ |
|||
/// <summary>
|
|||
/// Increments the reference counter.
|
|||
/// </summary>
|
|||
void AddRef(); |
|||
|
|||
/// <summary>
|
|||
/// Decrements the reference counter.
|
|||
/// </summary>
|
|||
void ReleaseRef(); |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory.Internals |
|||
{ |
|||
/// <summary>
|
|||
/// Implements reference counting lifetime guard mechanism similar to the one provided by <see cref="SafeHandle"/>,
|
|||
/// but without the restriction of the guarded object being a handle.
|
|||
/// </summary>
|
|||
internal abstract class RefCountedLifetimeGuard : IDisposable |
|||
{ |
|||
private int refCount = 1; |
|||
private int disposed; |
|||
private int released; |
|||
|
|||
~RefCountedLifetimeGuard() |
|||
{ |
|||
Interlocked.Exchange(ref this.disposed, 1); |
|||
this.ReleaseRef(); |
|||
} |
|||
|
|||
public bool IsDisposed => this.disposed == 1; |
|||
|
|||
public void AddRef() => Interlocked.Increment(ref this.refCount); |
|||
|
|||
public void ReleaseRef() |
|||
{ |
|||
Interlocked.Decrement(ref this.refCount); |
|||
if (this.refCount == 0) |
|||
{ |
|||
int wasReleased = Interlocked.Exchange(ref this.released, 1); |
|||
|
|||
if (wasReleased == 0) |
|||
{ |
|||
this.Release(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); |
|||
if (wasDisposed == 0) |
|||
{ |
|||
this.ReleaseRef(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
|
|||
protected abstract void Release(); |
|||
} |
|||
} |
|||
@ -1,72 +0,0 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Memory.Internals |
|||
{ |
|||
internal partial class UniformUnmanagedMemoryPool |
|||
{ |
|||
public class Buffer<T> : UnmanagedBuffer<T> |
|||
where T : struct |
|||
{ |
|||
private UniformUnmanagedMemoryPool pool; |
|||
|
|||
public Buffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) |
|||
: base(bufferHandle, length) => |
|||
this.pool = pool; |
|||
|
|||
/// <inheritdoc />
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (this.pool == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
this.pool.Return(this.BufferHandle); |
|||
this.pool = null; |
|||
this.BufferHandle = null; |
|||
} |
|||
|
|||
internal void MarkDisposed() |
|||
{ |
|||
this.pool = null; |
|||
this.BufferHandle = null; |
|||
} |
|||
} |
|||
|
|||
public sealed class FinalizableBuffer<T> : Buffer<T> |
|||
where T : struct |
|||
{ |
|||
public FinalizableBuffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) |
|||
: base(pool, bufferHandle, length) |
|||
{ |
|||
bufferHandle.AssignedToNewOwner(); |
|||
} |
|||
|
|||
// A VERY poorly written user code holding a Span<TPixel> on the stack,
|
|||
// while loosing the reference to Image<TPixel> (or disposing it) may write to (now unrelated) pool buffer,
|
|||
// or cause memory corruption if the underlying UmnanagedMemoryHandle has been released.
|
|||
// This is an unlikely scenario we mitigate a warning in DangerousGetRowSpan(i) APIs.
|
|||
#pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager<T> may permit memory to be freed while it is still in use by a Span<T>
|
|||
~FinalizableBuffer() => this.Dispose(false); |
|||
#pragma warning restore
|
|||
|
|||
protected override void Dispose(bool disposing) |
|||
{ |
|||
if (!disposing && this.BufferHandle != null) |
|||
{ |
|||
// We need to prevent handle finalization here.
|
|||
// See comments on UnmanagedMemoryHandle.Resurrect()
|
|||
this.BufferHandle.Resurrect(); |
|||
} |
|||
|
|||
base.Dispose(disposing); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Memory.Internals |
|||
{ |
|||
internal partial class UniformUnmanagedMemoryPool |
|||
{ |
|||
public UnmanagedBuffer<T> CreateGuardedBuffer<T>( |
|||
UnmanagedMemoryHandle handle, |
|||
int lengthInElements, |
|||
AllocationOptions options) |
|||
where T : struct |
|||
{ |
|||
var buffer = new UnmanagedBuffer<T>(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle)); |
|||
if (options.Has(AllocationOptions.Clean)) |
|||
{ |
|||
buffer.Clear(); |
|||
} |
|||
|
|||
return buffer; |
|||
} |
|||
|
|||
public RefCountedLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); |
|||
|
|||
private sealed class GroupLifetimeGuard : RefCountedLifetimeGuard |
|||
{ |
|||
private readonly UniformUnmanagedMemoryPool pool; |
|||
private readonly UnmanagedMemoryHandle[] handles; |
|||
|
|||
public GroupLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] handles) |
|||
{ |
|||
this.pool = pool; |
|||
this.handles = handles; |
|||
} |
|||
|
|||
protected override void Release() => this.pool.Return(this.handles); |
|||
} |
|||
|
|||
private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard |
|||
{ |
|||
private readonly UniformUnmanagedMemoryPool pool; |
|||
|
|||
public ReturnToPoolBufferLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle handle) |
|||
: base(handle) => |
|||
this.pool = pool; |
|||
|
|||
protected override void Release() => this.pool.Return(this.Handle); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
namespace SixLabors.ImageSharp.Memory.Internals |
|||
{ |
|||
/// <summary>
|
|||
/// Defines a strategy for managing unmanaged memory ownership.
|
|||
/// </summary>
|
|||
internal abstract class UnmanagedBufferLifetimeGuard : RefCountedLifetimeGuard |
|||
{ |
|||
private UnmanagedMemoryHandle handle; |
|||
|
|||
protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle; |
|||
|
|||
public UnmanagedMemoryHandle Handle => this.handle; |
|||
|
|||
public sealed class FreeHandle : UnmanagedBufferLifetimeGuard |
|||
{ |
|||
public FreeHandle(UnmanagedMemoryHandle handle) |
|||
: base(handle) |
|||
{ |
|||
} |
|||
|
|||
protected override void Release() => this.handle.Free(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,116 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using Microsoft.DotNet.RemoteExecutor; |
|||
using SixLabors.ImageSharp.Memory.Internals; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.Allocators |
|||
{ |
|||
public class RefCountingLifetimeGuardTests |
|||
{ |
|||
[Theory] |
|||
[InlineData(1)] |
|||
[InlineData(3)] |
|||
public void Dispose_ResultsInSingleRelease(int disposeCount) |
|||
{ |
|||
var guard = new MockLifetimeGuard(); |
|||
Assert.Equal(0, guard.ReleaseInvocationCount); |
|||
|
|||
for (int i = 0; i < disposeCount; i++) |
|||
{ |
|||
guard.Dispose(); |
|||
} |
|||
|
|||
Assert.Equal(1, guard.ReleaseInvocationCount); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Finalize_ResultsInSingleRelease() |
|||
{ |
|||
RemoteExecutor.Invoke(RunTest).Dispose(); |
|||
|
|||
static void RunTest() |
|||
{ |
|||
Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); |
|||
LeakGuard(false); |
|||
GC.Collect(); |
|||
GC.WaitForPendingFinalizers(); |
|||
Assert.Equal(1, MockLifetimeGuard.GlobalReleaseInvocationCount); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(1)] |
|||
[InlineData(3)] |
|||
public void AddRef_PreventsReleaseOnDispose(int addRefCount) |
|||
{ |
|||
var guard = new MockLifetimeGuard(); |
|||
|
|||
for (int i = 0; i < addRefCount; i++) |
|||
{ |
|||
guard.AddRef(); |
|||
} |
|||
|
|||
guard.Dispose(); |
|||
|
|||
for (int i = 0; i < addRefCount; i++) |
|||
{ |
|||
Assert.Equal(0, guard.ReleaseInvocationCount); |
|||
guard.ReleaseRef(); |
|||
} |
|||
|
|||
Assert.Equal(1, guard.ReleaseInvocationCount); |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddRef_PreventsReleaseOnFinalize() |
|||
{ |
|||
RemoteExecutor.Invoke(RunTest).Dispose(); |
|||
|
|||
static void RunTest() |
|||
{ |
|||
LeakGuard(true); |
|||
GC.Collect(); |
|||
GC.WaitForPendingFinalizers(); |
|||
Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void AddRefReleaseRefMisuse_DoesntLeadToMultipleReleases() |
|||
{ |
|||
var guard = new MockLifetimeGuard(); |
|||
guard.Dispose(); |
|||
guard.AddRef(); |
|||
guard.ReleaseRef(); |
|||
|
|||
Assert.Equal(1, guard.ReleaseInvocationCount); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
private static void LeakGuard(bool addRef) |
|||
{ |
|||
var guard = new MockLifetimeGuard(); |
|||
if (addRef) |
|||
{ |
|||
guard.AddRef(); |
|||
} |
|||
} |
|||
|
|||
private class MockLifetimeGuard : RefCountedLifetimeGuard |
|||
{ |
|||
public int ReleaseInvocationCount { get; private set; } |
|||
|
|||
public static int GlobalReleaseInvocationCount { get; private set; } |
|||
|
|||
protected override void Release() |
|||
{ |
|||
this.ReleaseInvocationCount++; |
|||
GlobalReleaseInvocationCount++; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Linq; |
|||
using Microsoft.DotNet.RemoteExecutor; |
|||
using SixLabors.ImageSharp.Memory.Internals; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.Allocators |
|||
{ |
|||
public class SharedArrayPoolBufferTests |
|||
{ |
|||
[Fact] |
|||
public void AllocatesArrayPoolArray() |
|||
{ |
|||
RemoteExecutor.Invoke(RunTest).Dispose(); |
|||
|
|||
static void RunTest() |
|||
{ |
|||
using (var buffer = new SharedArrayPoolBuffer<byte>(900)) |
|||
{ |
|||
Assert.Equal(900, buffer.GetSpan().Length); |
|||
buffer.GetSpan().Fill(42); |
|||
} |
|||
|
|||
byte[] array = ArrayPool<byte>.Shared.Rent(900); |
|||
byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); |
|||
|
|||
Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void OutstandingReferences_RetainArrays() |
|||
{ |
|||
RemoteExecutor.Invoke(RunTest).Dispose(); |
|||
|
|||
static void RunTest() |
|||
{ |
|||
var buffer = new SharedArrayPoolBuffer<byte>(900); |
|||
Span<byte> span = buffer.GetSpan(); |
|||
|
|||
buffer.AddRef(); |
|||
((IDisposable)buffer).Dispose(); |
|||
span.Fill(42); |
|||
byte[] array = ArrayPool<byte>.Shared.Rent(900); |
|||
Assert.NotEqual(42, array[0]); |
|||
ArrayPool<byte>.Shared.Return(array); |
|||
|
|||
buffer.ReleaseRef(); |
|||
array = ArrayPool<byte>.Shared.Rent(900); |
|||
byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); |
|||
Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); |
|||
ArrayPool<byte>.Shared.Return(array); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
// Copyright (c) Six Labors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Collections.Generic; |
|||
using Microsoft.DotNet.RemoteExecutor; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.Memory.Internals; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Memory.Allocators |
|||
{ |
|||
public class UnmanagedBufferTests |
|||
{ |
|||
public class AllocatorBufferTests : BufferTestSuite |
|||
{ |
|||
public AllocatorBufferTests() |
|||
: base(new UnmanagedMemoryAllocator(1024 * 64)) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Allocate_CreatesValidBuffer() |
|||
{ |
|||
using var buffer = UnmanagedBuffer<int>.Allocate(10); |
|||
Span<int> span = buffer.GetSpan(); |
|||
Assert.Equal(10, span.Length); |
|||
span[9] = 123; |
|||
Assert.Equal(123, span[9]); |
|||
} |
|||
|
|||
[Fact] |
|||
public unsafe void Dispose_DoesNotReleaseOutstandingReferences() |
|||
{ |
|||
RemoteExecutor.Invoke(RunTest).Dispose(); |
|||
|
|||
static void RunTest() |
|||
{ |
|||
var buffer = UnmanagedBuffer<int>.Allocate(10); |
|||
Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); |
|||
Span<int> span = buffer.GetSpan(); |
|||
|
|||
// Pin should AddRef
|
|||
using (MemoryHandle h = buffer.Pin()) |
|||
{ |
|||
int* ptr = (int*)h.Pointer; |
|||
((IDisposable)buffer).Dispose(); |
|||
Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); |
|||
ptr[3] = 13; |
|||
Assert.Equal(13, span[3]); |
|||
} // Unpin should ReleaseRef
|
|||
|
|||
Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(2)] |
|||
[InlineData(12)] |
|||
public void BufferFinalization_TracksAllocations(int count) |
|||
{ |
|||
RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); |
|||
|
|||
static void RunTest(string countStr) |
|||
{ |
|||
int countInner = int.Parse(countStr); |
|||
List<UnmanagedBuffer<byte>> l = FillList(countInner); |
|||
|
|||
l.RemoveRange(0, l.Count / 2); |
|||
|
|||
GC.Collect(); |
|||
GC.WaitForPendingFinalizers(); |
|||
|
|||
Assert.Equal(countInner / 2, l.Count); // This is here to prevent eager finalization of the list's elements
|
|||
Assert.Equal(countInner / 2, UnmanagedMemoryHandle.TotalOutstandingHandles); |
|||
} |
|||
|
|||
static List<UnmanagedBuffer<byte>> FillList(int countInner) |
|||
{ |
|||
var l = new List<UnmanagedBuffer<byte>>(); |
|||
for (int i = 0; i < countInner; i++) |
|||
{ |
|||
var h = UnmanagedBuffer<byte>.Allocate(42); |
|||
l.Add(h); |
|||
} |
|||
|
|||
return l; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue