Skip to content

Commit cd7c91c

Browse files
committed
move AsyncLocal out of core lib and into testing
1 parent 558ff99 commit cd7c91c

File tree

4 files changed

+80
-74
lines changed

4 files changed

+80
-74
lines changed
Lines changed: 21 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

4+
using System;
45
using System.Threading;
56

67
namespace SixLabors.ImageSharp.Diagnostics
@@ -15,10 +16,8 @@ namespace SixLabors.ImageSharp.Diagnostics
1516
/// </summary>
1617
public static class MemoryDiagnostics
1718
{
18-
internal static readonly InteralMemoryDiagnostics Default = new();
19-
private static AsyncLocal<InteralMemoryDiagnostics> localInstance = null;
19+
private static int totalUndisposedAllocationCount;
2020

21-
// the async local end up out of scope during finalizers so putting into thte internal class is useless
2221
private static UndisposedAllocationDelegate undisposedAllocation;
2322
private static int undisposedAllocationSubscriptionCounter;
2423
private static readonly object SyncRoot = new();
@@ -49,42 +48,34 @@ public static event UndisposedAllocationDelegate UndisposedAllocation
4948
}
5049
}
5150

52-
internal static InteralMemoryDiagnostics Current
53-
{
54-
get
55-
{
56-
if (localInstance != null && localInstance.Value != null)
57-
{
58-
return localInstance.Value;
59-
}
60-
61-
return Default;
62-
}
63-
64-
set
65-
{
66-
if (localInstance == null)
67-
{
68-
lock (SyncRoot)
69-
{
70-
localInstance ??= new AsyncLocal<InteralMemoryDiagnostics>();
71-
}
72-
}
51+
/// <summary>
52+
/// Fires when ImageSharp allocates memory from a MemoryAllocator
53+
/// </summary>
54+
internal static event Action MemoryAllocated;
7355

74-
localInstance.Value = value;
75-
}
76-
}
56+
/// <summary>
57+
/// Fires when ImageSharp releases allocated from a MemoryAllocator
58+
/// </summary>
59+
internal static event Action MemoryReleased;
7760

7861
/// <summary>
7962
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
8063
/// </summary>
81-
public static int TotalUndisposedAllocationCount => Current.TotalUndisposedAllocationCount;
64+
public static int TotalUndisposedAllocationCount => totalUndisposedAllocationCount;
8265

8366
internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0;
8467

85-
internal static void IncrementTotalUndisposedAllocationCount() => Current.IncrementTotalUndisposedAllocationCount();
68+
internal static void IncrementTotalUndisposedAllocationCount()
69+
{
70+
Interlocked.Increment(ref totalUndisposedAllocationCount);
71+
MemoryAllocated?.Invoke();
72+
}
8673

87-
internal static void DecrementTotalUndisposedAllocationCount() => Current.DecrementTotalUndisposedAllocationCount();
74+
internal static void DecrementTotalUndisposedAllocationCount()
75+
{
76+
Interlocked.Decrement(ref totalUndisposedAllocationCount);
77+
MemoryReleased?.Invoke();
78+
}
8879

8980
internal static void RaiseUndisposedMemoryResource(string allocationStackTrace)
9081
{
@@ -105,21 +96,5 @@ internal static void RaiseUndisposedMemoryResource(string allocationStackTrace)
10596
allocationStackTrace);
10697
#endif
10798
}
108-
109-
internal class InteralMemoryDiagnostics
110-
{
111-
private int totalUndisposedAllocationCount;
112-
113-
/// <summary>
114-
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
115-
/// </summary>
116-
public int TotalUndisposedAllocationCount => this.totalUndisposedAllocationCount;
117-
118-
internal void IncrementTotalUndisposedAllocationCount() =>
119-
Interlocked.Increment(ref this.totalUndisposedAllocationCount);
120-
121-
internal void DecrementTotalUndisposedAllocationCount() =>
122-
Interlocked.Decrement(ref this.totalUndisposedAllocationCount);
123-
}
12499
}
125100
}

tests/ImageSharp.Tests/MemoryAllocatorValidator.cs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,76 @@
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
5-
using System.Diagnostics;
5+
using System.Threading;
66
using SixLabors.ImageSharp.Diagnostics;
77
using Xunit;
88

99
namespace SixLabors.ImageSharp.Tests
1010
{
1111
public static class MemoryAllocatorValidator
1212
{
13-
public static IDisposable MonitorAllocations(int max = 0)
13+
private static readonly AsyncLocal<TestMemoryDiagnostics> LocalInstance = new();
14+
15+
public static bool MonitoringAllocations => LocalInstance.Value != null;
16+
17+
static MemoryAllocatorValidator()
1418
{
15-
MemoryDiagnostics.Current = new();
16-
return new TestMemoryAllocatorDisposable(max);
19+
MemoryDiagnostics.MemoryAllocated += MemoryDiagnostics_MemoryAllocated;
20+
MemoryDiagnostics.MemoryReleased += MemoryDiagnostics_MemoryReleased;
1721
}
1822

19-
public static void ValidateAllocation(int max = 0)
23+
private static void MemoryDiagnostics_MemoryReleased()
2024
{
21-
var count = MemoryDiagnostics.TotalUndisposedAllocationCount;
22-
var pass = count <= max;
23-
Assert.True(pass, $"Expected a max of {max} undisposed buffers but found {count}");
25+
TestMemoryDiagnostics backing = LocalInstance.Value;
26+
if (backing != null)
27+
{
28+
backing.TotalRemainingAllocated--;
29+
}
30+
}
2431

25-
if (count > 0)
32+
private static void MemoryDiagnostics_MemoryAllocated()
33+
{
34+
TestMemoryDiagnostics backing = LocalInstance.Value;
35+
if (backing != null)
2636
{
27-
Debug.WriteLine("We should have Zero undisposed memory allocations.");
37+
backing.TotalAllocated++;
38+
backing.TotalRemainingAllocated++;
2839
}
40+
}
2941

30-
MemoryDiagnostics.Current = null;
42+
public static TestMemoryDiagnostics MonitorAllocations()
43+
{
44+
var diag = new TestMemoryDiagnostics();
45+
LocalInstance.Value = diag;
46+
return diag;
3147
}
3248

33-
public struct TestMemoryAllocatorDisposable : IDisposable
49+
public static void StopMonitoringAllocations() => LocalInstance.Value = null;
50+
51+
public static void ValidateAllocations(int expectedAllocationCount = 0)
52+
=> LocalInstance.Value?.Validate(expectedAllocationCount);
53+
54+
public class TestMemoryDiagnostics : IDisposable
3455
{
35-
private readonly int max;
56+
public int TotalAllocated { get; set; }
3657

37-
public TestMemoryAllocatorDisposable(int max) => this.max = max;
58+
public int TotalRemainingAllocated { get; set; }
59+
60+
public void Validate(int expectedAllocationCount)
61+
{
62+
var count = this.TotalRemainingAllocated;
63+
var pass = expectedAllocationCount == count;
64+
Assert.True(pass, $"Expected a {expectedAllocationCount} undisposed buffers but found {count}");
65+
}
3866

3967
public void Dispose()
40-
=> ValidateAllocation(this.max);
68+
{
69+
this.Validate(0);
70+
if (LocalInstance.Value == this)
71+
{
72+
StopMonitoringAllocations();
73+
}
74+
}
4175
}
4276
}
4377
}

tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ public override Image<TPixel> GetImage(IImageDecoder decoder)
159159
return this.LoadImage(decoder);
160160
}
161161

162-
// do cache so we can track allocation correctly when validating memory
163-
if (MemoryDiagnostics.Current != MemoryDiagnostics.Default)
162+
// do not cache so we can track allocation correctly when validating memory
163+
if (MemoryAllocatorValidator.MonitoringAllocations)
164164
{
165165
return this.LoadImage(decoder);
166166
}

tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,23 @@ namespace SixLabors.ImageSharp.Tests
1111
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
1212
public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribute
1313
{
14-
private readonly int max = 0;
14+
private readonly int expected = 0;
1515

1616
public ValidateDisposedMemoryAllocationsAttribute()
1717
: this(0)
1818
{
1919
}
2020

21-
public ValidateDisposedMemoryAllocationsAttribute(int max)
22-
{
23-
this.max = max;
24-
if (max > 0)
25-
{
26-
Debug.WriteLine("Needs fixing, we shoudl have Zero undisposed memory allocations.");
27-
}
28-
}
21+
public ValidateDisposedMemoryAllocationsAttribute(int expected)
22+
=> this.expected = expected;
2923

3024
public override void Before(MethodInfo methodUnderTest)
31-
=> MemoryAllocatorValidator.MonitorAllocations(this.max); // the disposable isn't important cause the validate below does the same thing
25+
=> MemoryAllocatorValidator.MonitorAllocations();
3226

3327
public override void After(MethodInfo methodUnderTest)
34-
=> MemoryAllocatorValidator.ValidateAllocation(this.max);
28+
{
29+
MemoryAllocatorValidator.ValidateAllocations(this.expected);
30+
MemoryAllocatorValidator.StopMonitoringAllocations();
31+
}
3532
}
3633
}

0 commit comments

Comments
 (0)