Ninject compact framework 2.2.0.0 memory leaks

157 views
Skip to first unread message

Nishith

unread,
May 6, 2011, 3:21:24 AM5/6/11
to ninject
I have written small unittest framework for Ninject for memory leak.
While running the test cases, i observed that there is a huge memory
leak in ninject.
In 100 cycle i am getting leak of around 9kb.
if same operation i am doing with normal object creation (i.e with new
()) i do not get any memory leak.

Please see the below code. Is ninject usage is correct?

namespace UnitTest.Framework.DependencyInjection
{
using System;
using Ninject;
using Ninject.Modules;
using NUnit.Framework;

/// <summary>
/// Fruit color interface
/// </summary>
public interface IFruitColor
{
}

/// <summary>
/// Dummy implementation of fruitcolor
/// </summary>
public class ColorRed : IFruitColor
{
}

/// <summary>
/// Dummy implementation of fruitcolor
/// </summary>
public class ColorYellow : IFruitColor
{
}

/// <summary>
/// Empty fruit interface
/// </summary>
public abstract class Fruit
{
/// <summary>
/// Fruit color member
/// </summary>
public readonly IFruitColor FruitColor;

/// <summary>
/// Initializes a new instance of the <see cref="Fruit"/>
class.
/// </summary>
/// <param name="fruitColor">
/// The fruit color.
/// </param>
protected Fruit(IFruitColor fruitColor)
{
this.FruitColor = fruitColor;
}
}

/// <summary>
/// Apple class
/// </summary>
public class AppleMock : Fruit
{
/// <summary>
/// Initializes a new instance of the <see cref="AppleMock"/>
class.
/// </summary>
/// <param name="fruitColor">
/// The fruit color.
/// </param>
public AppleMock(IFruitColor fruitColor)
: base(fruitColor)
{
}
}

/// <summary>
/// Banana class
/// </summary>
public class BananaMock : Fruit
{
/// <summary>
/// Initializes a new instance of the <see cref="BananaMock"/>
class.
/// </summary>
/// <param name="fruitColor">
/// The fruit color.
/// </param>
public BananaMock(IFruitColor fruitColor)
: base(fruitColor)
{
}
}

/// <summary>
/// Ninject module that illustrates a Banana basket
/// </summary>
public class BananaBasket : NinjectModule
{
/// <summary>
/// Binds fruit to bananas
/// </summary>
public override void Load()
{
this.Bind<Fruit>().To<BananaMock>().InTransientScope();

this.Bind<IFruitColor>().To<ColorYellow>().WhenInjectedInto<BananaMock>();
}
}

/// <summary>
/// Ninject module that illustrates a apple basket
/// </summary>
public class AppleBasket : NinjectModule
{
/// <summary>
/// Binds fruit to apple in singleton scope
/// </summary>
public override void Load()
{
this.Bind<Fruit>().To<AppleMock>().InSingletonScope();

this.Bind<IFruitColor>().To<ColorRed>().WhenInjectedInto<AppleMock>();
}
}

/// <summary>
/// A very simple test of the DI container
/// </summary>
[TestFixture]
public class NinjectDiContainer
{
/// <summary>
/// Kernel field
/// </summary>
private IKernel kernel;

/// <summary>
/// Module field
/// </summary>
private NinjectModule module;

/// <summary>
/// Creates a kernel and setups banana basket as default
module.
/// </summary>
[SetUp]
public void SetupContainer()
{
this.module = new BananaBasket();
this.kernel = new StandardKernel(this.module);
}


[Test]
public void MemoryInTransientScopeTest()
{
this.kernel.Unload(this.module.Name);
var bananamodule = new BananaBasket();
this.kernel.Load(bannnamodule);
GC.Collect();
var before = GC.GetTotalMemory(true);
for (var i = 0; i < 100; i++)
{
var fruit = this.kernel.Get<Fruit>();
}

GC.Collect();
var after = GC.GetTotalMemory(true);
Assert.Equals(before, after);

// Normal object creation
for (var i = 0; i < 100; i++)
{
var fruit = new BananaMock(new ColorYellow());
}

GC.Collect();
var last= GC.GetTotalMemory(true);
Assert.Equals(last, after);
}

/// <summary>
/// Switches module to apple basket instead of banana basket
and
/// verifies that returned fruit are now apples
/// </summary>
[Test]
public void MemoryInSingletonScopeTest()
{
this.kernel.Unload(this.module.Name);
var applemodule = new AppleBasket();
this.kernel.Load(applemodule);
GC.Collect();
var before = GC.GetTotalMemory(true);
for (var i = 0; i < 100; i++)
{
var fruit = this.kernel.Get<Fruit>();
}

GC.Collect();
var after = GC.GetTotalMemory(true);
Assert.Equals(before, after);

// Normal object creation
for (var i = 0; i < 100; i++)
{
var fruit = new AppleMock(new ColorRed());
}

GC.Collect();
var last= GC.GetTotalMemory(true);
Assert.Equals(last, after);
}
}

/// <summary>
/// Disposes the kernel
/// </summary>
[TearDown]
public void DisposeContainer()
{
this.kernel.Dispose();
}
}
}

Remo Gloor

unread,
May 6, 2011, 8:57:08 AM5/6/11
to nin...@googlegroups.com
It's not that easy:
1. Ninject will create some internal data and dynamic classes for each binding. This means the first gall to get will create data that is not released until the kernel is disposed,
2. Ninject has an internal timer that will release some memory regularly. The interval can be configured.

In order to get good data you have to call get at least once before taking a memory snapshot. And give ninject some time after the last call to do its internal cleanup.

Nishith Desai

unread,
May 11, 2011, 1:55:22 AM5/11/11
to nin...@googlegroups.com
Thanks for the information.
How I can configure the Interval? Where I can find this?

Thanks & Regards,
Nishith


--
You received this message because you are subscribed to the Google Groups "ninject" group.
To post to this group, send email to nin...@googlegroups.com.
To unsubscribe from this group, send email to ninject+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/ninject?hl=en.

Nishith Desai

unread,
May 13, 2011, 8:21:25 AM5/13/11
to nin...@googlegroups.com

I did a quick check on the Ninject properties and this is what I get…

this.Kernel.Settings.CachePruningInterval

I checked the internal, it is 30 seconds

It is read only property.

The help says that it prunes the cache memory at certain intervals.

Now i am keeping my application ideal for long time to give some time to Ninject the release some memory. I am taking the memory reading after 30 sec, 60 sec and 120 sec. But I am not finding any difference in memory. It's not releasing the memory.

If i dispose the kernel object, I am getting all the memory back. But without kernel dispose i am not getting memory back.


Thanks & Regards,

Nishith

Joe Wasson

unread,
May 13, 2011, 1:35:40 PM5/13/11
to nin...@googlegroups.com
Your tests are flawed. By virtue of asking for a type Ninject will
create some internal data structures, as Remo said. That is not a
memory leak as you say, when you dispose the kernel object you get the
memory back. The thing you need to be testing is that memory does not
increase as a function of calls to .Get(). The first call to .Get()
will create memory as those structures are created and retained but
subsequent cals to .Get() should not be creating additional memory.
In other words, memory consumption should be O(1), not O(n) (where n
is the number of identical calls to .Get()). You will also want to
check that no actual fruit objects are being leaked, probably by
creating a static member that is InterlockIncremented on create and
InterlockDecremented on finalize.

Finally, I question the validity of using GC.GetTotalMemory, so much
else can happen in the runtime to create memory in separate threads
that you can't control that the test has the possibility of randomly
failing and I don't like tests that can randomly fail.

Nishith Desai

unread,
May 17, 2011, 12:29:41 AM5/17/11
to nin...@googlegroups.com
The thing you are telling about Get(), memory consumption should be O(1), not O(n) (where n is the number of identical calls to .Get()). This is not happening. First Get() call is consuming approx 3 to 4 kb in .Net compact framework and around 9k in win32. After that every Get() call is consuming 32 bytes of GC memory in .Net Compact framework. These 32 bytes are concerned for me.
Message has been deleted

Nishith Desai

unread,
May 31, 2011, 2:48:01 AM5/31/11
to nin...@googlegroups.com

Remo

I agree to your comments, but the point I make is related to the difference between compact framework version and the normal version for Ninject

We get good results for normal windows version of Ninject

But with compact framework we have noticed that the memory is consumed for each Kernel.Get<T>, even if T is of same type.


for (var i = 1; i <= 100; i++)
{
    var fruit = this.kernel.Get<IFruit>();
    GC.Collect();
     var end = GC.GetTotalMemory(true);
}



On Tue, May 17, 2011 at 4:42 PM, Remo Gloor <remo....@bbv.ch> wrote:
As already said it's not that easy. So much is going on internally. If
you want to check if there is no memory leak the only way to profe is
to use a memory profiler an analyze all the new/changed objects.

E.g. there are collections involved that get cleaned at a configurable
interval. But the .NET implementation of the collections will only
delete the referenced objects but keep the internal data structures
for the size that was used. This internal data structure is reused and
only grow if more space is required.



On May 17, 6:29 am, Nishith Desai <ndesai1...@gmail.com> wrote:
> The thing you are telling about Get(), memory consumption should be O(1),
> not O(n) (where n is the number of identical calls to .Get()). This is not
> happening. First Get() call is consuming approx 3 to 4 kb in .Net compact
> framework and around 9k in win32. After that every Get() call is consuming
> 32 bytes of GC memory in .Net Compact framework. These 32 bytes are
> concerned for me.
>
>
>
>
>
>
>
> On Fri, May 13, 2011 at 11:05 PM, Joe Wasson <jwas...@gmail.com> wrote:
> > Your tests are flawed.  By virtue of asking for a type Ninject will
> > create some internal data structures, as Remo said.  That is not a
> > memory leak as you say, when you dispose the kernel object you get the
> > memory back.  The thing you need to be testing is that memory does not
> > increase as a function of calls to .Get().  The first call to .Get()
> > will create memory as those structures are created and retained but
> > subsequent cals to .Get() should not be creating additional memory.
> > In other words, memory consumption should be O(1), not O(n) (where n
> > is the number of identical calls to .Get()). You will also want to
> > check that no actual fruit objects are being leaked, probably by
> > creating a static member that is InterlockIncremented on create and
> > InterlockDecremented on finalize.
>
> > Finally, I question the validity of using GC.GetTotalMemory, so much
> > else can happen in the runtime to create memory in separate threads
> > that you can't control that the test has the possibility of randomly
> > failing and I don't like tests that can randomly fail.
>
> > On Fri, May 13, 2011 at 5:21 AM, Nishith Desai <ndesai1...@gmail.com>

> > wrote:
> > > I did a quick check on the Ninject properties and this is what I get…
>
> > > this.Kernel.Settings.CachePruningInterval
>
> > > I checked the internal, it is 30 seconds
>
> > > It is read only property.
>
> > > The help says that it prunes the cache memory at certain intervals.
>
> > > Now i am keeping my application ideal for long time to give some time to
> > > Ninject the release some memory. I am taking the memory reading after 30
> > > sec, 60 sec and 120 sec. But I am not finding any difference in memory.
> > It's
> > > not releasing the memory.
>
> > > If i dispose the kernel object, I am getting all the memory back. But
> > > without kernel dispose i am not getting memory back.
>
> > > Thanks & Regards,
>
> > > Nishith
>
> > > On Wed, May 11, 2011 at 11:25 AM, Nishith Desai <ndesai1...@gmail.com>

> > > wrote:
>
> > >> Thanks for the information.
> > >> How I can configure the Interval? Where I can find this?
>
> > >> Thanks & Regards,
> > >> Nishith
>

Remo Gloor

unread,
May 31, 2011, 6:36:05 PM5/31/11
to ninject
I had a closer look at the CF implementation. Indeed there is a
problem in the cache. It will be solved in a upcomming maintenance
release. But also be aware that your Unittests will not give you much
different results. They need to be quite different to have about the
same memory usage before and after resolving.

TWischmeier

unread,
Jun 1, 2011, 2:34:42 AM6/1/11
to nin...@googlegroups.com
Hi Remo,

can you elaborate a bit on the problem? I would like to now, what the error is and how it might effect our software. And of course I would like to do a correction, if possible.

Remo Gloor

unread,
Jun 1, 2011, 5:00:29 AM6/1/11
to ninject
This only affects CF and SL builds any other builds are not affected.

Some WeakReference instances will not be removed from the cache. But
the object instances themselves are removed as they are only weak
referenced.

It's already fixed on the master. There is another issue for CF which
I want to fix before doing a bugfix release.

Nishith

unread,
Jun 1, 2011, 6:49:52 AM6/1/11
to ninject
Remo,

Thanks for the response.
Please, can you tell me, when i can expect the fix version? It will be
great help for us if this issue will be resolved.
Thanks in advance.

Remo Gloor

unread,
Jun 1, 2011, 10:17:50 AM6/1/11
to ninject
I don't give any ETA as it highly depends on how much time I can put
into the project.

But feel free to either download the current continuous integration
build 2.3.0.x from teamcity or get the Maintenance_V2.2 branch and
pick the commit https://github.com/ninject/ninject/commit/917f7fd85f6dcb0055d2530e181ddda83ff5127a
from the master.
Reply all
Reply to author
Forward
0 new messages