在调试版本中,垃圾收集会引发奇怪的bug
本文关键字:bug 版本 调试 | 更新日期: 2023-09-27 17:49:30
我正在编写垃圾收集测试,在调试模式下偶然发现了一组奇怪的错误。这是一个蒸馏的POC代码。有一个包含20个lambda的列表和一个对它们的弱引用列表。我访问第13个lambda(调用. tostring()),然后清除列表。然后强制垃圾收集,并分析哪些元素在清理中幸存下来。
public void TestGC_WTF() {
var handlers = new List<Action>();
var weakReferences = new List<WeakReference>();
int testValue = 0;
for (int i = 0; i < 20; i++) {
int number = i;
Action handler = () => testValue += number;
handlers.Add(handler);
weakReferences.Add(new WeakReference(handler));
handler = null;
}
handlers[13].ToString();
handlers.Clear();
GC.Collect();
if (false) { } //This is required for the bug to occur.
var aliveReferences = Enumerable.Range(0, weakReferences.Count).Where(i => weakReferences[i].IsAlive).ToArray();
Console.WriteLine("Uncollected handlers: {0}", string.Join(",", aliveReferences));
}
在调试模式下,此代码打印("未收集的处理程序:13,19")。我对这个结果的问题如下:
- 不收集第19个元素(即使我在每次循环迭代结束时显式地将句柄变量设置为null)
- 第13个元素不被收集(即使我从未将其存储在任何地方)
-
if (false) { }
(或任何其他循环/条件)是触发错误的必要条件。
是什么导致了这些问题?(我知道在调试中,对象被保存到定义它们的块的末尾。单凭这一点仍然不能解释我遇到的任何问题。
我已经向微软提交了这个bug,但是我对问题的原因更感兴趣https://connect.microsoft.com/VisualStudio/feedback/details/775082/strangely-triggered-strange-bugs-with-garbage-collection-in-debug-builds
这是特定于x86抖动的。是的,这看起来像一个bug,抖动将堆栈帧上的临时存储位置标记为有效的对象引用。与[ebp-74h]一样,存储处理程序[13]引用的插槽和[ebp-68h]保存对处理程序[19]的引用的插槽。这些临时代码在x86代码中很常见,因为cpu寄存器的数量很少,并且当优化器被禁用时,抖动在寻找少数寄存器的良好使用方面花费的精力很少。
你可以在connect.microsoft.com上提交反馈报告。然而,他们修复它的几率非常小,当优化器被禁用时,抖动没有义务使此工作。局部变量的生命周期被扩展到方法的末尾,以便于调试。对于声明的局部变量以及抖动分配的临时变量为True。当然,这最终无关紧要,您只会将发布版本交付给您的客户。当优化的发布版本也出现问题时,请随时提出这个问题,这对GC的有效性是有害的。
我不相信GC.Collect()
一定会收集所有可能的垃圾,只是花费精力这样做。在调试构建中,优化倾向于保持引用活动,因为您更有可能希望使用调试器检查它们。我没有看到任何我称之为bug的行为