I was running a load tester against an application and looking at a
memory profiler. I noticed pretty significant memory growth and
starting digging into it. My instances were being rooted by Ninject.
This was worrisome. After changing code to hardcoded dependencies, I
re-ran the test and the memory leak was gone.
I started to dig more deeply into the issue, specifically paying
attention to what was holding on to my references - CacheEntry
objects. Looking at how Ninject manages scoped objects, I realized
that Ninject essentially adds the scope to an internal collection via
a WeakReference. Then, via a timer, the collection is pruned of
unreferenced objects.
This seems like a clever solution to me (the bad kind of clever). I
see some benefits to the approach, but I decided to make a slight
changes (this is all off of 2.0 trunk code). In the Cache object, I
changed the Remember and TryGet method to store objects directly in
the HttpContext.Items collection (when appropriate).
The beginning of Remember looks like:
Ensure.ArgumentNotNull(context, "context");
var scope = context.GetScope();
#if !NO_WEB
if (scope is System.Web.HttpContext)
{
((System.Web.HttpContext)scope).Items.Add(context.Binding,
reference.Instance);
return;
}
#endif
The beginning of TryGet looks like:
Ensure.ArgumentNotNull(context, "context");
var scope = context.GetScope();
#if !NO_WEB
if (scope is System.Web.HttpContext)
{
return ((System.Web.HttpContext) scope).Items
[context.Binding];
}
#endif
I ran my load test with and without this change, you can see the
difference in memory usage here:
http://twitpic.com/pi7pr/full
The load test attempts to simulate real load, so there's a lot of
randomness to it. The difference is spikes is understandable given the
how items are purged from Ninject (on a timer); however, it does seem
like the original code is taking up more memory and (top pink), has
more live instances (bottom pink) and more total instances (middle
red) even after the purges.