views:

247

answers:

3

Just the other day I was investigating a memory leak that was ballooning the app from ~50MB to ~130MB in under two minutes. Turns out that the problem was with the ConcurrentQueue class. Internally, the class stores a linked list of arrays. When an item is dequeued from the ConcurrentQueue, the index in the array is bumped, but the item remains in the array (i.e. it's not set to null). The entire array node is dropped after enough enqueues/dequeues, so it's not technically a leak, but if putting large objects in the ConcurrentQueue, this can get out of hand fast. The documentation makes no note of this danger.

I was wondering what other potential memory pitfalls are in the Base Class Library? I know about the Substring one (that is, if you call substring and just hold onto the result, the whole string will still be in memory). Any others you've encountered?

A: 

Though not a direct memory leak or specific to .net/BCL, there is the string concatenation (using the += operator) issue. That's pretty CPU intensive in loops due to heavy garbage collecting.

lasseeskildsen
Yup. Java solved this by converting it to use a StringBuilder; I don't know why .NET hasn't done the same.
Robert Fraser
.NET does have the same it's just under System.Text
Aren
The VS C# compiler is also smart enough to convert expressions like ("This " + "is" + " string #" + stringNumber) into StringBuilder internally, if it makes sense to do so.
Dan Bryant
@Dan Bryant: Fortunately you are not correct. For the case you mention, the compiler would actually emit a call to `string.Concat("This is string#", stringNumber)`. Literals are combined where possible, and the call is to `string.Concat` because it is faster than `StringBuilder` for a fixed number of append operations.
280Z28
@280Z28, excellent, it's even smarter than I realized.
Dan Bryant
+3  A: 

You are correct. The bug is located in the method System.Collections.Concurrent.ConcurrentQueue<T>+Segment.TryRemove(out T, ref ConcurrentQueue<T>.Segment).

If you look at this method in Reflector, you'll see the following line:

result = this.m_array[low];

There should be the following line after it:

this.m_array[low] = default(T);

For reference, you can see how this is correctly implemented in the method System.Collections.Generic.Queue<T>.Dequeue().

280Z28
Yup; saw this in Reflector, too -- though I'm not sure if t would somehow break atomicity/threading?
Robert Fraser
No, it's definitely a bug. The bucket is protected in that region, and the code is designed to ensure that the value it contains is only ever read once.
280Z28
BTW, I originally sent a report on this to someone at MS on Apr. 1. I just don't think they've had a chance to fix it yet. Unfortunately, the class is completely unusable until the issue is resolved.
280Z28
Reported to Microsoft: https://connect.microsoft.com/VisualStudio/feedback/details/552868/memory-leak-in-concurrentqueue-t-class-dequeued-enteries-are-still-rooted
Robert Fraser
Robert Fraser
@Robert Fraser: I didn't file it at connect.microsoft.com because by the time I found it all I could do was drop a quick line to my superior and CC a contact at MS while I rewrote the code to use `Queue<T>` instead.
280Z28
@280Z28 - Thanks for the vote of confidence. it looks like it was fixed, but they probably won't push it out until the next service pack.
Robert Fraser
A: 

Robert, could you please provide the repro code? Thanks!

q = new ConcurrentQueue(); q.Enqueue(something); q.TryDequeue(out something);
Robert Fraser
For a more robust test case you can check: https://connect.microsoft.com/VisualStudio/feedback/details/552868/memory-leak-in-concurrentqueue-t-class-dequeued-enteries-are-still-rooted
Robert Fraser