Skip to content

Commit ad4e8d4

Browse files
committed
[mono][tests] Add bridge tests
1 parent 3cf419c commit ad4e8d4

File tree

4 files changed

+455
-0
lines changed

4 files changed

+455
-0
lines changed
+392
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using System.Threading;
10+
11+
// False pinning cases are still possible. For example the thread can die
12+
// and its stack reused by another thread. It also seems that a thread that
13+
// does a GC can keep on the stack references to objects it encountered
14+
// during the collection which are never released afterwards. This would
15+
// be more likely to happen with the interpreter which reuses more stack.
16+
public static class FinalizerHelpers
17+
{
18+
private static IntPtr aptr;
19+
20+
private static unsafe void NoPinActionHelper(int depth, Action act)
21+
{
22+
// Avoid tail calls
23+
int* values = stackalloc int[20];
24+
aptr = new IntPtr(values);
25+
26+
if (depth <= 0)
27+
{
28+
//
29+
// When the action is called, this new thread might have not allocated
30+
// anything yet in the nursery. This means that the address of the first
31+
// object that would be allocated would be at the start of the tlab and
32+
// implicitly the end of the previous tlab (address which can be in use
33+
// when allocating on another thread, at checking if an object fits in
34+
// this other tlab). We allocate a new dummy object to avoid this type
35+
// of false pinning for most common cases.
36+
//
37+
new object();
38+
act();
39+
ClearStack();
40+
}
41+
else
42+
{
43+
NoPinActionHelper(depth - 1, act);
44+
}
45+
}
46+
47+
private static unsafe void ClearStack()
48+
{
49+
int* values = stackalloc int[25000];
50+
for (int i = 0; i < 25000; i++)
51+
values[i] = 0;
52+
}
53+
54+
public static void PerformNoPinAction(Action act)
55+
{
56+
Thread thr = new Thread(() => NoPinActionHelper (128, act));
57+
thr.Start();
58+
thr.Join();
59+
}
60+
}
61+
62+
public class BridgeBase
63+
{
64+
public static int fin_count;
65+
66+
~BridgeBase()
67+
{
68+
fin_count++;
69+
}
70+
}
71+
72+
public class Bridge : BridgeBase
73+
{
74+
public List<object> Links = new List<object>();
75+
public int __test;
76+
77+
~Bridge()
78+
{
79+
Links = null;
80+
}
81+
}
82+
83+
public class Bridge1 : BridgeBase
84+
{
85+
public object Link;
86+
~Bridge1()
87+
{
88+
Link = null;
89+
}
90+
}
91+
92+
// 128 size
93+
public class Bridge14 : BridgeBase
94+
{
95+
public object a,b,c,d,e,f,g,h,i,j,k,l,m,n;
96+
}
97+
98+
public class NonBridge
99+
{
100+
public object Link;
101+
}
102+
103+
public class NonBridge2 : NonBridge
104+
{
105+
public object Link2;
106+
}
107+
108+
public class NonBridge14
109+
{
110+
public object a,b,c,d,e,f,g,h,i,j,k,l,m,n;
111+
}
112+
113+
114+
public class BridgeTest
115+
{
116+
const int OBJ_COUNT = 100 * 1000;
117+
const int LINK_COUNT = 2;
118+
const int EXTRAS_COUNT = 0;
119+
const double survival_rate = 0.1;
120+
121+
// Pathological case for the original old algorithm. Goes
122+
// away when merging is replaced by appending with flag
123+
// checking.
124+
static void SetupLinks()
125+
{
126+
var list = new List<Bridge>();
127+
for (int i = 0; i < OBJ_COUNT; ++i)
128+
{
129+
var bridge = new Bridge();
130+
list.Add(bridge);
131+
}
132+
133+
var r = new Random(100);
134+
for (int i = 0; i < OBJ_COUNT; ++i)
135+
{
136+
var n = list[i];
137+
for (int j = 0; j < LINK_COUNT; ++j)
138+
n.Links.Add(list[r.Next (OBJ_COUNT)]);
139+
for (int j = 0; j < EXTRAS_COUNT; ++j)
140+
n.Links.Add(j);
141+
if (r.NextDouble() <= survival_rate)
142+
n.__test = 1;
143+
}
144+
}
145+
146+
const int LIST_LENGTH = 10000;
147+
const int FAN_OUT = 10000;
148+
149+
// Pathological case for the new algorithm. Goes away with
150+
// the single-node elimination optimization, but will still
151+
// persist if modified by using a ladder instead of the single
152+
// list.
153+
static void SetupLinkedFan()
154+
{
155+
var head = new Bridge();
156+
var tail = new NonBridge();
157+
head.Links.Add(tail);
158+
for (int i = 0; i < LIST_LENGTH; ++i)
159+
{
160+
var obj = new NonBridge ();
161+
tail.Link = obj;
162+
tail = obj;
163+
}
164+
var list = new List<Bridge>();
165+
tail.Link = list;
166+
for (int i = 0; i < FAN_OUT; ++i)
167+
list.Add (new Bridge());
168+
}
169+
170+
// Pathological case for the improved old algorithm. Goes
171+
// away with copy-on-write DynArrays, but will still persist
172+
// if modified by using a ladder instead of the single list.
173+
static void SetupInverseFan()
174+
{
175+
var tail = new Bridge();
176+
object list = tail;
177+
for (int i = 0; i < LIST_LENGTH; ++i)
178+
{
179+
var obj = new NonBridge();
180+
obj.Link = list;
181+
list = obj;
182+
}
183+
var heads = new Bridge[FAN_OUT];
184+
for (int i = 0; i < FAN_OUT; ++i)
185+
{
186+
var obj = new Bridge();
187+
obj.Links.Add(list);
188+
heads[i] = obj;
189+
}
190+
}
191+
192+
// Not necessarily a pathology, but a special case of where we
193+
// generate lots of "dead" SCCs. A non-bridge object that
194+
// can't reach a bridge object can safely be removed from the
195+
// graph. In this special case it's a linked list hanging off
196+
// a bridge object. We can handle this by "forwarding" edges
197+
// going to non-bridge nodes that have only a single outgoing
198+
// edge. That collapses the whole list into a single node.
199+
// We could remove that node, too, by removing non-bridge
200+
// nodes with no outgoing edges.
201+
static void SetupDeadList()
202+
{
203+
var head = new Bridge();
204+
var tail = new NonBridge();
205+
head.Links.Add(tail);
206+
for (int i = 0; i < LIST_LENGTH; ++i)
207+
{
208+
var obj = new NonBridge();
209+
tail.Link = obj;
210+
tail = obj;
211+
}
212+
}
213+
214+
// Triggered a bug in the forwarding mechanic.
215+
static void SetupSelfLinks()
216+
{
217+
var head = new Bridge();
218+
var tail = new NonBridge();
219+
head.Links.Add(tail);
220+
tail.Link = tail;
221+
}
222+
223+
const int L0_COUNT = 50000;
224+
const int L1_COUNT = 50000;
225+
const int EXTRA_LEVELS = 4;
226+
227+
// Set a complex graph from one bridge to a couple.
228+
// The graph is designed to expose naive coloring on
229+
// tarjan and SCC explosion on classic.
230+
static void Spider()
231+
{
232+
Bridge a = new Bridge();
233+
Bridge b = new Bridge();
234+
235+
var l1 = new List<object>();
236+
for (int i = 0; i < L0_COUNT; ++i) {
237+
var l0 = new List<object>();
238+
l0.Add(a);
239+
l0.Add(b);
240+
l1.Add(l0);
241+
}
242+
var last_level = l1;
243+
for (int l = 0; l < EXTRA_LEVELS; ++l) {
244+
int j = 0;
245+
var l2 = new List<object>();
246+
for (int i = 0; i < L1_COUNT; ++i) {
247+
var tmp = new List<object>();
248+
tmp.Add(last_level [j++ % last_level.Count]);
249+
tmp.Add(last_level [j++ % last_level.Count]);
250+
l2.Add(tmp);
251+
}
252+
last_level = l2;
253+
}
254+
Bridge c = new Bridge();
255+
c.Links.Add(last_level);
256+
}
257+
258+
// Simulates a graph with two nested cycles that is produces by
259+
// the async state machine when `async Task M()` method gets its
260+
// continuation rooted by an Action held by RunnableImplementor
261+
// (ie. the task continuation is hooked through the SynchronizationContext
262+
// implentation and rooted only by Android bridge objects).
263+
static void NestedCycles()
264+
{
265+
Bridge runnableImplementor = new Bridge ();
266+
Bridge byteArrayOutputStream = new Bridge ();
267+
NonBridge2 action = new NonBridge2 ();
268+
NonBridge displayClass = new NonBridge ();
269+
NonBridge2 asyncStateMachineBox = new NonBridge2 ();
270+
NonBridge2 asyncStreamWriter = new NonBridge2 ();
271+
272+
runnableImplementor.Links.Add(action);
273+
action.Link = displayClass;
274+
action.Link2 = asyncStateMachineBox;
275+
displayClass.Link = action;
276+
asyncStateMachineBox.Link = asyncStreamWriter;
277+
asyncStateMachineBox.Link2 = action;
278+
asyncStreamWriter.Link = byteArrayOutputStream;
279+
asyncStreamWriter.Link2 = asyncStateMachineBox;
280+
}
281+
282+
static void RunGraphTest(Action test)
283+
{
284+
Console.WriteLine("Start test {0}", test.Method.Name);
285+
FinalizerHelpers.PerformNoPinAction(test);
286+
Console.WriteLine("-graph built-");
287+
for (int i = 0; i < 5; i++)
288+
{
289+
Console.WriteLine("-GC {0}/5-", i);
290+
GC.Collect ();
291+
GC.WaitForPendingFinalizers();
292+
}
293+
294+
Console.WriteLine("Finished test {0}, finalized {1}", test.Method.Name, Bridge.fin_count);
295+
}
296+
297+
static void TestLinkedList()
298+
{
299+
int count = Environment.ProcessorCount + 2;
300+
var th = new Thread [count];
301+
for (int i = 0; i < count; ++i)
302+
{
303+
th [i] = new Thread( _ =>
304+
{
305+
var lst = new ArrayList();
306+
for (var j = 0; j < 500 * 1000; j++)
307+
{
308+
lst.Add (new object());
309+
if ((j % 999) == 0)
310+
lst.Add (new BridgeBase());
311+
if ((j % 1000) == 0)
312+
new BridgeBase();
313+
if ((j % 50000) == 0)
314+
lst = new ArrayList();
315+
}
316+
});
317+
318+
th [i].Start();
319+
}
320+
321+
for (int i = 0; i < count; ++i)
322+
th [i].Join();
323+
324+
GC.Collect(2);
325+
Console.WriteLine("Finished test LinkedTest, finalized {0}", BridgeBase.fin_count);
326+
}
327+
328+
//we fill 16Mb worth of stuff, eg, 256k objects
329+
const int major_fill = 1024 * 256;
330+
331+
//4mb nursery with 64 bytes objects -> alloc half
332+
const int nursery_obj_count = 16 * 1024;
333+
334+
static void SetupFragmentation<TBridge, TNonBridge>()
335+
where TBridge : new()
336+
where TNonBridge : new()
337+
{
338+
const int loops = 4;
339+
for (int k = 0; k < loops; k++)
340+
{
341+
Console.WriteLine("[{0}] CrashLoop {1}/{2}", DateTime.Now, k + 1, loops);
342+
var arr = new object[major_fill];
343+
for (int i = 0; i < major_fill; i++)
344+
arr[i] = new TNonBridge();
345+
GC.Collect(1);
346+
Console.WriteLine("[{0}] major fill done", DateTime.Now);
347+
348+
//induce massive fragmentation
349+
for (int i = 0; i < major_fill; i += 4)
350+
{
351+
arr[i + 1] = null;
352+
arr[i + 2] = null;
353+
arr[i + 3] = null;
354+
}
355+
GC.Collect (1);
356+
Console.WriteLine("[{0}] fragmentation done", DateTime.Now);
357+
358+
//since 50% is garbage, do 2 fill passes
359+
for (int j = 0; j < 2; ++j)
360+
{
361+
for (int i = 0; i < major_fill; i++)
362+
{
363+
if ((i % 1000) == 0)
364+
new TBridge();
365+
else
366+
arr[i] = new TBridge();
367+
}
368+
}
369+
Console.WriteLine("[{0}] done spewing bridges", DateTime.Now);
370+
371+
for (int i = 0; i < major_fill; i++)
372+
arr[i] = null;
373+
GC.Collect ();
374+
}
375+
}
376+
377+
public static int Main(string[] args)
378+
{
379+
// TestLinkedList(); // Crashes, but only in this multithreaded variant
380+
RunGraphTest(SetupFragmentation<Bridge14, NonBridge14>); // This passes but the following crashes ??
381+
// RunGraphTest(SetupFragmentation<Bridge, NonBridge>);
382+
RunGraphTest(SetupLinks);
383+
RunGraphTest(SetupLinkedFan);
384+
RunGraphTest(SetupInverseFan);
385+
386+
RunGraphTest(SetupDeadList);
387+
RunGraphTest(SetupSelfLinks);
388+
RunGraphTest(NestedCycles); // Fixed by Filip
389+
// RunGraphTest(Spider); // Crashes
390+
return 100;
391+
}
392+
}

0 commit comments

Comments
 (0)