mono/mono — pull request #990 by BrandonLWhite (2014-04-11 23:48:34+00:00)
Title: [runtime] Fixed leak of MonoListItems in MonoCQ

This change eliminates a memory leak I tracked down using Socket BeginRead. You can run the code at https://gist.github.com/BrandonLWhite/10509361 and watch the memory usage. What got me started on this path was use of SignalR and Katana/OWIN self-host causing my application to suck up all of the Mono heap.

Note that once the heap size hits the limit, the application doesn't crash. So it's as if the GC does start reclaiming space, but not until the limit is reached. If you set `MONO_GC_PARAMS=max-heap-size=16M` you can see what I'm talking about. Memory use will climb to the limit, then hold steady. If you increase the limit and rerun, you can watch it climb up even further to that limit.

I've reproduced this issue on ARMv5 Debian Wheezy, and x64 Ubuntu Precise. I'm using sgen. I didn't try Boehm.

Here is a distilled heapshot. All of the prior and subsequent ones show these same objects increasing in count.

1168848 3377 346 System.Object[] (bytes: +41536, count: +118)	[_MonoCQItem::array]
2 root references (0 pinning)
3301 references from: System.MonoCQItem
...

315192 3321 94 System.Byte[] (bytes: +10384, count: +118)	[_MonoCQItem::array_state]
7 root references (1 pinning)
3301 references from: System.MonoCQItem
...

79224 3301 24 System.MonoCQItem (bytes: +2832, count: +118)
1 root references (1 pinning)
3300 references from: System.MonoListItem

52880 3305 16 System.MonoListItem (bytes: +1904, count: +119)	[MonoMList]
2 root references (1 pinning)
3298 references from: System.MonoListItem

What I discovered is that the GC wasn't collecting the threadpool's concurrent queue (MonoCQ) items that were no longer in use. I found that whenever the CQ is done with a `MonoCQItem` and goes to remove it from the linked-list (mono_cq_remove_node), if I NULL out it's `next` reference pointing to the new head then the GC dutifully reclaims the objects.

With this change, you can clearly see the test's memory use holding very stable as expected. It doesn't climb up to the limit any more.

I do admit that I'm not 100% certain why clearing the old_head's `next` reference causes the GC to then properly collect it. It seems to me like you shouldn't need to do that. Any insight into this would be appreciated.