Reputation: 6821
I'm writing garbage collection tests and have stumbled upon a strange set of bugs that occur in debug mode. Here is a the distilled POC code. There is a list of 20 lambdas and a list of weak references to them. I access the 13th lambda (calling .ToString()) and then clear the list. Then force garbage collection and analyze which elements have survived the cleanup.
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));
}
In debug mode this code prints ("Uncollected handlers: 13,19". The problems that I have with this result are as follows:
if (false) { }
(or any other loop/conditional) is essential for triggering the bug.What causes there problems? (I know that in debug the objects are preserved until the end of the block where they're defined. This alone still doesn't explain any of the problems that I have.)
I've submitted the bug to Microsoft, but I'm more interested in the cause of the problems https://connect.microsoft.com/VisualStudio/feedback/details/775082/strangely-triggered-strange-bugs-with-garbage-collection-in-debug-builds
Upvotes: 3
Views: 217
Reputation: 942358
This is specific to the x86 jitter. Yes, this looks like a bug, the jitter is marking temporary storage locations on the stack frame as valid object references. Like [ebp-74h], the slot where the handler[13] reference is stored and [ebp-68h] which keeps a reference to handler[19]. These temporaries are common in x86 code due the low number of cpu registers and the little effort the jitter expends in finding good use of the few registers there are when the optimizer is disabled.
You can file a feedback report at connect.microsoft.com. The odds that they'll fix it is however very small, the jitter has no obligation to make this work when the optimizer is disabled. The lifetime of local variables is extended to the end of the method to make debugging easy. True for declared local variables as well as temporary ones that the jitter allocates. And of course it ultimately matters little, you'll only ever ship the Release build to your customers. Please do feel free to bring this up when the optimized Release build litters as well, that's detrimental to GC effectiveness.
Upvotes: 3
Reputation: 60997
I don't believe GC.Collect()
will necessarily collect all possible garbage, only expend effort doing so. In a debug build, optimizations favor keeping references alive, since you are more likely to want to inspect them with the debugger. I don't see any behavior here that I would call a bug.
Upvotes: 2