// Run a single test pass on the node set. Null out node references according to the given CollectStyle, // run a garbage collection and then verify that each node is either live or dead as we predict. Take care // of the order in which test runs are made against a single TestSet: e.g. running a CollectStyle.All will // collect all nodes, rendering further runs relatively uninteresting. public void Run(CollectStyle cs) { Console.WriteLine("Running test TS:{0} CS:{1} {2} entries...", Enum.GetName(typeof(TableStyle), m_style), Enum.GetName(typeof(CollectStyle), cs), m_count); // Iterate over the array of nodes deciding for each whether to sever the reference (null out the // entry). for (int i = 0; i < m_count; i++) { bool sever = false; switch (cs) { case CollectStyle.All: // Sever all references. sever = true; break; case CollectStyle.None: // Don't sever any references. break; case CollectStyle.Alternate: // Sever every second reference (starting with the first). if ((i % 2) == 0) { sever = true; } break; case CollectStyle.Random: // Sever any reference with a 50% probability. if (m_rng.Next(100) > 50) { sever = true; } break; } if (sever) { m_nodes[i] = null; } } // Initialize a full GC and wait for all finalizers to complete (so we get an accurate picture of // which nodes were collected). GC.Collect(); GC.WaitForPendingFinalizers(); // Calculate our own view of which nodes should be alive or dead. Use a simple mark array for this. // Once the algorithm is complete a true value at a given index in the array indicates a node that // should still be alive, otherwise the node should have been collected. // Initialize the mark array. Set true for nodes we still have a strong reference to from the array // (these should definitely not have been collected yet). Set false for the other nodes (we assume // they must have been collected until we prove otherwise). for (int i = 0; i < m_count; i++) { m_marks[i] = m_nodes[i] != null; } // Perform multiple passes over the handles we allocated (or our recorded version of the handles at // least). If we find a handle with a marked (live) primary where the secondary is not yet marked then // go ahead and mark that secondary (dependent handles are defined to do this: primaries act as if // they have a strong reference to the secondary up until the point they are collected). Repeat this // until we manage a scan over the entire table without marking any additional nodes as live. At this // point the marks array should reflect which objects are still live. while (true) { // Assume we're not going any further nodes to mark as live. bool marked = false; // Look at each handle in turn. for (int i = 0; i < m_handleCount; i++) { if (m_marks[m_handles[i].m_primary]) { // Primary is live. if (!m_marks[m_handles[i].m_secondary]) { // Secondary wasn't marked as live yet. Do so and remember that we marked at least // node as live this pass (so we need to loop again since this secondary could be the // same as a primary earlier in the table). m_marks[m_handles[i].m_secondary] = true; marked = true; } } } // Terminate the loop if we scanned the entire table without marking any additional nodes as live // (since additional scans can't make any difference). if (!marked) { break; } } // Validate our view of node liveness (m_marks) correspond to reality (m_nodes and m_collected). for (int i = 0; i < m_count; i++) { // Catch nodes which still have strong references but have collected anyway. This is stricly a // subset of the next test but it would be a very interesting bug to call out. if (m_nodes[i] != null && m_collected[i]) { Error(String.Format("Node {0} was collected while it still had a strong root", i)); } // Catch nodes which we compute as alive but have been collected. if (m_marks[i] && m_collected[i]) { Error(String.Format("Node {0} was collected while it was still reachable", i)); } // Catch nodes which we compute as dead but haven't been collected. if (!m_marks[i] && !m_collected[i]) { Error(String.Format("Node {0} wasn't collected even though it was unreachable", i)); } } }
// Run a single test pass on the node set. Null out node references according to the given CollectStyle, // run a garbage collection and then verify that each node is either live or dead as we predict. Take care // of the order in which test runs are made against a single TestSet: e.g. running a CollectStyle.All will // collect all nodes, rendering further runs relatively uninteresting. public void Run(CollectStyle cs) { Console.WriteLine("Running test TS:{0} CS:{1} {2} entries...", Enum.GetName(typeof(TableStyle), m_style), Enum.GetName(typeof(CollectStyle), cs), m_count); // Iterate over the array of nodes deciding for each whether to sever the reference (null out the // entry). for (int i = 0; i < m_count; i++) { bool sever = false; switch (cs) { case CollectStyle.All: // Sever all references. sever = true; break; case CollectStyle.None: // Don't sever any references. break; case CollectStyle.Alternate: // Sever every second reference (starting with the first). if ((i % 2) == 0) sever = true; break; case CollectStyle.Random: // Sever any reference with a 50% probability. if (m_rng.Next(100) > 50) sever = true; break; } if (sever) m_nodes[i] = null; } // Initialize a full GC and wait for all finalizers to complete (so we get an accurate picture of // which nodes were collected). GC.Collect(); GC.WaitForPendingFinalizers(); // Calculate our own view of which nodes should be alive or dead. Use a simple mark array for this. // Once the algorithm is complete a true value at a given index in the array indicates a node that // should still be alive, otherwise the node should have been collected. // Initialize the mark array. Set true for nodes we still have a strong reference to from the array // (these should definitely not have been collected yet). Set false for the other nodes (we assume // they must have been collected until we prove otherwise). for (int i = 0; i < m_count; i++) m_marks[i] = m_nodes[i] != null; // Perform multiple passes over the handles we allocated (or our recorded version of the handles at // least). If we find a handle with a marked (live) primary where the secondary is not yet marked then // go ahead and mark that secondary (dependent handles are defined to do this: primaries act as if // they have a strong reference to the secondary up until the point they are collected). Repeat this // until we manage a scan over the entire table without marking any additional nodes as live. At this // point the marks array should reflect which objects are still live. while (true) { // Assume we're not going any further nodes to mark as live. bool marked = false; // Look at each handle in turn. for (int i = 0; i < m_handleCount; i++) if (m_marks[m_handles[i].m_primary]) { // Primary is live. if (!m_marks[m_handles[i].m_secondary]) { // Secondary wasn't marked as live yet. Do so and remember that we marked at least // node as live this pass (so we need to loop again since this secondary could be the // same as a primary earlier in the table). m_marks[m_handles[i].m_secondary] = true; marked = true; } } // Terminate the loop if we scanned the entire table without marking any additional nodes as live // (since additional scans can't make any difference). if (!marked) break; } // Validate our view of node liveness (m_marks) correspond to reality (m_nodes and m_collected). for (int i = 0; i < m_count; i++) { // Catch nodes which still have strong references but have collected anyway. This is stricly a // subset of the next test but it would be a very interesting bug to call out. if (m_nodes[i] != null && m_collected[i]) Error(String.Format("Node {0} was collected while it still had a strong root", i)); // Catch nodes which we compute as alive but have been collected. if (m_marks[i] && m_collected[i]) Error(String.Format("Node {0} was collected while it was still reachable", i)); // Catch nodes which we compute as dead but haven't been collected. if (!m_marks[i] && !m_collected[i]) Error(String.Format("Node {0} wasn't collected even though it was unreachable", i)); } }