-
-
Notifications
You must be signed in to change notification settings - Fork 865
/
Copy pathUniformUnmanagedMemoryPoolTests.Trim.cs
173 lines (144 loc) · 6.89 KB
/
UniformUnmanagedMemoryPoolTests.Trim.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Tests.Memory.Allocators;
public partial class UniformUnmanagedMemoryPoolTests
{
[Collection(nameof(NonParallelTests))]
public class Trim
{
[CollectionDefinition(nameof(NonParallelTests), DisableParallelization = true)]
public class NonParallelTests
{
}
[Fact]
public void TrimPeriodElapsed_TrimsHalfOfUnusedArrays()
{
RemoteExecutor.Invoke(RunTest).Dispose();
static void RunTest()
{
var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 5_000 };
var pool = new UniformUnmanagedMemoryPool(128, 256, trimSettings);
UnmanagedMemoryHandle[] a = pool.Rent(64);
UnmanagedMemoryHandle[] b = pool.Rent(64);
pool.Return(a);
Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles);
Thread.Sleep(15_000);
// We expect at least 2 Trim actions, first trim 32, then 16 arrays.
// 128 - 32 - 16 = 80
Assert.True(
UnmanagedMemoryHandle.TotalOutstandingHandles <= 80,
$"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 80");
pool.Return(b);
}
}
// Complicated Xunit ceremony to disable parallel execution of an individual test,
// MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed,
// which is strongly timing-sensitive, and might be flaky under high load.
[CollectionDefinition(nameof(NonParallelCollection), DisableParallelization = true)]
public class NonParallelCollection
{
}
[Collection(nameof(NonParallelCollection))]
public class NonParallel
{
public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS;
// TODO: Investigate failures on macOS. All handles are released after GC.
// (It seems to happen more consistently on .NET 6.)
[ConditionalFact(nameof(IsNotMacOS))]
public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed()
{
if (!TestEnvironment.RunsOnCI)
{
// This may fail in local runs resulting in high memory load.
// Remove the condition for local debugging!
return;
}
if (TestEnvironment.OSArchitecture == Architecture.Arm64)
{
// Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342
return;
}
RemoteExecutor.Invoke(RunTest).Dispose();
static void RunTest()
{
var trimSettings1 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 6_000 };
var pool1 = new UniformUnmanagedMemoryPool(128, 256, trimSettings1);
Thread.Sleep(8_000); // Let some callbacks fire already
var trimSettings2 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 3_000 };
var pool2 = new UniformUnmanagedMemoryPool(128, 256, trimSettings2);
pool1.Return(pool1.Rent(64));
pool2.Return(pool2.Rent(64));
Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles);
// This exercises pool weak reference list trimming:
LeakPoolInstance();
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles);
Thread.Sleep(15_000);
Assert.True(
UnmanagedMemoryHandle.TotalOutstandingHandles <= 64,
$"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64");
GC.KeepAlive(pool1);
GC.KeepAlive(pool2);
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void LeakPoolInstance()
{
var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 4_000 };
_ = new UniformUnmanagedMemoryPool(128, 256, trimSettings);
}
}
}
public static readonly bool Is32BitProcess = !Environment.Is64BitProcess;
private static readonly List<byte[]> PressureArrays = new();
[Fact]
public static void GC_Collect_OnHighLoad_TrimsEntirePool()
{
if (!Is32BitProcess)
{
// This test is only relevant for 32-bit processes.
return;
}
RemoteExecutor.Invoke(RunTest).Dispose();
static void RunTest()
{
Assert.False(Environment.Is64BitProcess);
const int oneMb = 1 << 20;
var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { HighPressureThresholdRate = 0.2f };
GCMemoryInfo memInfo = GC.GetGCMemoryInfo();
int highLoadThreshold = (int)(memInfo.HighMemoryLoadThresholdBytes / oneMb);
highLoadThreshold = (int)(trimSettings.HighPressureThresholdRate * highLoadThreshold);
var pool = new UniformUnmanagedMemoryPool(oneMb, 16, trimSettings);
pool.Return(pool.Rent(16));
Assert.Equal(16, UnmanagedMemoryHandle.TotalOutstandingHandles);
for (int i = 0; i < highLoadThreshold; i++)
{
byte[] array = new byte[oneMb];
TouchPage(array);
PressureArrays.Add(array);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers(); // The pool should be fully trimmed after this point
Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles);
// Prevent eager collection of the pool:
GC.KeepAlive(pool);
static void TouchPage(byte[] b)
{
uint size = (uint)b.Length;
const uint pageSize = 4096;
uint numPages = size / pageSize;
for (uint i = 0; i < numPages; i++)
{
b[i * pageSize] = (byte)(i % 256);
}
}
}
}
}
}