In relation to my previous question, I need to check whether a component that will be instantiated by Castle Windsor, can be garbage collected after my code has finished using it. I have tried the suggestion in the answers from the previous question, but it does not seem to work as expected, at least for my code. So I would like to write a unit test that tests whether a specific object instance can be garbage collected after some of my code has run.
Is that possible to do in a reliable way ?
EDIT
I currently have the following test based on Paul Stovell's answer, which succeeds:
[TestMethod]
public void ReleaseTest()
{
WindsorContainer container = new WindsorContainer();
container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy();
container.AddComponentWithLifestyle<ReleaseTester>(LifestyleType.Transient);
Assert.AreEqual(0, ReleaseTester.refCount);
var weakRef = new WeakReference(container.Resolve<ReleaseTester>());
Assert.AreEqual(1, ReleaseTester.refCount);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.AreEqual(0, ReleaseTester.refCount, "Component not released");
}
private class ReleaseTester
{
public static int refCount = 0;
public ReleaseTester()
{
refCount++;
}
~ReleaseTester()
{
refCount--;
}
}
Am I right assuming that, based on the test above, I can conclude that Windsor will not leak memory when using the NoTrackingReleasePolicy ?
This is what I normally do:
[Test]
public void MyTest()
{
WeakReference reference;
new Action(() =>
{
var service = new Service();
// Do things with service that might cause a memory leak...
reference = new WeakReference(service, true);
})();
// Service should have gone out of scope about now,
// so the garbage collector can clean it up
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsNull(reference.Target);
}
NB: There are very, very few times where you should call GC.Collect() in a production application. But testing for leaks is one example of where it's appropriate.
Perhaps you could hold a WeakReference to it and then check to see that it no longer alive (i.e., !IsAlive) after the tests have completed.
Based on Paul's answer, I created a more reusable Assert method. Since string
's are copied by value I added an explicit check for them. They can be collected by the garbage collector.
public static void IsGarbageCollected<TObject>( ref TObject @object )
where TObject : class
{
Action<TObject> emptyAction = o => { };
IsGarbageCollected( ref @object, emptyAction );
}
public static void IsGarbageCollected<TObject>(
ref TObject @object,
Action<TObject> useObject )
where TObject : class
{
if ( typeof( TObject ) == typeof( string ) )
{
// Strings are copied by value, and don't leak anyhow.
return;
}
int generation = GC.GetGeneration( @object );
useObject( @object );
WeakReference reference = new WeakReference( @object, true );
@object = null;
// The object should have gone out of scope about now,
// so the garbage collector can clean it up.
GC.Collect( generation, GCCollectionMode.Forced );
GC.WaitForPendingFinalizers();
Assert.IsNull( reference.Target );
}
The following unit tests show the function is working in some common scenarios.
[TestMethod]
public void IsGarbageCollectedTest()
{
// Empty object without any references which are held.
object empty = new object();
AssertHelper.IsGarbageCollected( ref empty );
// Strings are copied by value, but are collectable!
string @string = "";
AssertHelper.IsGarbageCollected( ref @string );
// Keep reference around.
object hookedEvent = new object();
#pragma warning disable 168
object referenceCopy = hookedEvent;
#pragma warning restore 168
AssertHelper.ThrowsException<AssertFailedException>(
() => AssertHelper.IsGarbageCollected( ref hookedEvent ) );
GC.KeepAlive( referenceCopy );
// Still attached as event.
Publisher publisher = new Publisher();
Subscriber subscriber = new Subscriber( publisher );
AssertHelper.ThrowsException<AssertFailedException>(
() => AssertHelper.IsGarbageCollected( ref subscriber ) );
GC.KeepAlive( publisher );
}
Due to differences when using the Release
configuration (I assume compiler optimizations), some of these unit tests would fail if GC.KeepAlive()
were not to be called.
Complete source code (including some of the helper methods used) can be found in my library.
Use dotMemory Unit framework (it's free)
[TestMethod]
public void ReleaseTest()
{
// arrange
WindsorContainer container = new WindsorContainer();
container.Kernel.ReleasePolicy = new NoTrackingReleasePolicy();
container.AddComponentWithLifestyle<ReleaseTester>(LifestyleType.Transient);
var target = container.Resolve<ReleaseTester>()
// act
target = null;
// assert
dotMemory.Check(memory =>
Assert.AreEqual(
0,
memory.GetObjects(where => where.Type.Is<ReleaseTester>().ObjectsCount,
"Component not released");
}
This is not an answer, however you may want to try running your code in both Debug and Release modes (for comparison sake).
In my experience the Debug version of JIT'ed code is made easier to debug and thus may see references stay alive longer (I belive function scope) However, code JITed in Release mode may have the objects ready for collection quickly once it is out of scope and if a Collection happens.
Also not answering your question: :-)
I would be interested in seeing you debug this code using Visual Studio in Interop mode (Managed and Native) and then breaking after displaying a message box or something. Then you can open the Debug->Windows-Immediate and then type
load sos
(Change to thread 0)
!dso
!do <object>
!gcroot <object> (and look for any roots)
(or you can use Windbg as other's have posted in previous posts)
Thanks, Aaron
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With