views:

789

answers:

10

I work on a project where there is a huge number of objects being instanced by a few classes that stay in memory for the lifetime of the application. There are a lot of memory leaks being caused with OutOfMemoryExceptions being thrown every now and again. It seems like after the instantiated objects ago out of scope, they are not being garbage collected.

I have isolated the problem to being mostly about the event handlers that are attached to the long-living object that are never detached, thus causing the long-living object to still have a reference to the out of scope objects, which then will never be garbage collected.

The solution that has been proposed by my colleagues is as follows: Implement IDisposable on all classes, across the board and in the Dispose method, null all the references in your objects and detach from all event that you attached to.

I believe this is a really really bad idea. Firstly because it's 'overkill' since the problem can be mostly solved by fixing a few problem areas and secondly because the purpose of IDisposable is to release any unmanaged resources your objects control, not because you don't trust the garbage collector. So far my arguments have fallen on deaf ears. How can I convince them that this is futile?

+8  A: 

Point them at Joe Duffy's post about IDisposable/finalizers - combined wisdom of many smart people.

I'm currently finding it hard to see a statement there saying "don't implement it when you don't need it" - but aside from anything else, showing them the complexity involved in implementing it properly may well help to dissuade them from it...

Unfortunately, if people won't listen, they won't listen. Try to get them to explain why they think they need IDisposable. Do they think the garbage collector doesn't work? Show them that it works. If you can convince them that it's doing no good (for most types) then surely they'll stop adding work for themselves...

As Brian says, implementing IDisposable isn't going to help with the event problem on its own - it needs to actually be called by something. Finalizers aren't going to help you in this case either. They really need to explicitly do something to remove the event handlers.

Jon Skeet
+1 for the link
Jehof
+9  A: 

Just implementing Dispose() across all types is not going to solve your problem. Remember that Dispose() is not automatically called and it has nothing to do with reclaiming managed memory. In order to have any effect of your Dispose() methods, you need to call it in all relevant place - either explicitly or via using.

In other words just implementing IDisposable all around will not magically solve your problems cause the Dispose() methods will not be called unless you also change the usage of every type in your code.

However, I would not recommend implementing IDisposable on all your types, simply because it makes no sense. The interface is used to indicate that the type in question uses some resource, which isn't handled by the garbage collector.

Event references are handled by the garbage collector. You just need to unsubscribe if your publisher lives significantly longer than your subscribers. Once the publisher dies the subscribers will die as well.

Brian Rasmussen
Ok, I have left out one detail. The plan is to call dispose on all objects that are instantiated before they go out of scope.
michael_erasmus
That is surely a much greater task than just making sure you detach subscribers as needed.
Brian Rasmussen
@Brian - what, even with the `using` keyword?
Daniel Earwicker
@Earwicker: sorry, I'm not sure what you're asking. Please elaborate so I can clarify my answer if needed.
Brian Rasmussen
@Earwicker - the using keyword is the easy part. Implementing IDispose on all your classes is the great task
michael_erasmus
@Peter Rotham - hey, tell me about it. Actually the really hard part is pursuading Microsoft that implementing `IDisposable` deserves language support. The dream: http://smellegantcode.wordpress.com/2007/11/30/a-new-use-for-the-c-using-keyword/ - the reality: https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=294208
Daniel Earwicker
An alternative is to use C++/CLI which already has this.
Daniel Earwicker
+5  A: 

I once helped a coworker out on a similar problem with OutOfMemoryException errors occuring (caused by events leaving object references hanging around). The first thing I did was run the code through FXCop, which highlighted Dispose wasn't called on an IDisposable class.

Modifying that code to be disposed fixed the issue. Perhaps you should recommend FXCop be used?

Perhaps find the code with the issue in the source repository, run FXCop on it- see if it highlights the issue (it probably will if it is caused by a .NET Framework class) and use that to convince your coworkers.

RichardOD
+1  A: 

IDisposable is only meant to be used to release unmanaged resources (SafeHandles and the like), and the Dispose method propagates through the class heirarchy. It is not meant to try and get around bad unmanaged programming practices.

thecoop
I don't think I'd go quite that far. It's for anything which needs orderly clean-up. For example, if you have an object pool you may want to implement `IDisposable` so that underlying objects can be released back to the pool... that needn't mean there are any unmanaged resources involved.
Jon Skeet
An event is an unmanaged resource.
Daniel Earwicker
@Earwicker: No it's not. What makes you think it is? (Not ManualResetEvent etc, but an event declared with the event keyword.)
Jon Skeet
+1  A: 

Create duty on resolving one more OutOfMemoryException. When somebody resolves errors belong to others, it raises responsibility on own code. At early stage of our project we have been recommended "flag of fool" - responsible on fatal error gets this flag for a day.

Dewfy
+6  A: 

By coincidence I just posted this comment elsewhere:

An reference to an object being incorrectly retained is still a resource leak. This is why GC programs can still have leaks, usually due to the Observer pattern - the observer is on a list instead the observable and never gets taken off it. Ultimately, a remove is needed for every add, just as a delete is needed for every new. Exactly the same programming error, causing exactly the same problem. A "resource" is a really just a pair of functions that have to be called an equal number of times with corresponding arguments, and a "resource leak" is what happens when you fail to do that.

And you say:

the purpose of IDisposable is to release any Unmanaged resources your objects controls

Now, the += and -= operators on an event are effectively a pair of functions that you have to call an equal number of times with corresponding arguments (the event/handler pair being the corresponding arguments).

Therefore they constitute a resource. And as they are not dealt with (or "managed") by the GC for you, it can be helpful to think of them as just another kind of unmanaged resource. As Jon Skeet points out in the comments, unmanaged usually has a specific meaning, but in the context of IDisposable I think it's helpful to broaden it to include anything resource-like that has to be "torn down" after it has been "built up".

So event detaching is a very good candidate for handling with IDisposable.

Of course, you need to call Dispose somewhere, and you don't need to implement it on every single object (just those with event relationships that need management).

Also, bear in mind that if a pair of objects are connected by an event, and you "cast them adrift", by losing all references to them in all other objects, they don't keep each other alive. GC doesn't use reference counting. Once an object (or island of objects) is unreachable, it is up for being collected.

You only have to worry about objects enlisted as event handlers with events on objects that live a long time. e.g. a static event such as AppDomain.UnhandledException, or events on your application's main window.

Daniel Earwicker
"Unmanaged resource" has a pretty specific meaning that isn't covered by events. There's nothing special about events here - it's just one object having an unwanted reference to another. Nothing unmanaged going on at all.
Jon Skeet
(I agree with the points about it being entirely reasonable to implement IDisposable, and that not every type should implement IDisposable - but I vehemently disagree with your use of the term "unmanaged resource".)
Jon Skeet
I also agree that an event isn't a 'unmanaged resource' per se, but I have the same solution in mind of implementing IDisposable on classes where events must be detached and ONLY on these classes. Alas, it seems like despite all my arguments and some of the very intelligent responses I got from here, the powers that be stand firm on their decision. Sigh.... What can you do?
michael_erasmus
Save your strength for another day...
Jon Skeet
I guess it depends whether you take *unmanaged* to mean "that which isn't compiled to IL" or "that which isn't dealt with by the GC". To explain the full value of `IDisposable`, we can either say it's applicable to "unmanaged resources and also things written in managed code but that are otherwise like unmanaged resources", or we can broaden "unmanaged resources" to mean resources not cleaned up by the GC. As a user of a library I shouldn't care what language it's implemented in. I should care how to use it correctly, is it thread-safe, etc. That's why I prefer the broader definiton.
Daniel Earwicker
See: http://msdn.microsoft.com/en-us/library/system.idisposable.aspx - to me it says that an unmanaged resource is one not dealt with by the GC. Any interpretation more restrictive than that would discard a lot of the value of `IDisposable`.
Daniel Earwicker
Made an edit to clarify the scope of the term "unmanaged" here, with ref. to Jon's comment.
Daniel Earwicker
@Earwicker: I am also in disagreement over your use of the term "unmanaged resources."The root distinction that drives what we clean up in Dispose() lies not specifically in whether the resource is managed or native, but whether we're cleaning up eagerly (Dispose() is called directly) or cleaning up due to object finalization. In the former you can clean up whatever you like, in the latter you don't know whether your managed references have already been disposed, so you don't touch them.
Curt Nichols
Cont'd: I think stretching the term "unmanaged resources" to include both native and managed resources is confusing and doesn't add clarity to the topic. More specifically, during finalization you cannot unsubscribe from an event because you don't know if the event object (or for that matter its owner) have been disposed--so describing the unsubscribe as "unmanaged" not only muddies the term but misses the point that one should only unsubscribe during eager disposal.
Curt Nichols
Cont'd: And eager dispose is the time when people generally understand they can clean up "managed resources," drawing further doubt as to why you would want to call them "unmanaged." Using a broader definition here doesn't seem to help. (Seriously, 600 characters isn't enough!)
Curt Nichols
Your perspective seems to be that finalization and dispose go together most of the time. That is unfortunately the impression given by a complete discussion like http://stackoverflow.com/questions/1243563/how-do-i-convince-my-colleagues-not-to-implement-idisposable-on-everything/1243597#1243597 - but they only go into finalizers in order to be complete. Actually finalizers are a niche thing, hardly any reason to write them these days. `IDisposable` is very frequently written without anything to do with finalizers. I'd urge you to look at how it's used in C++/CLI. Or...
Daniel Earwicker
... how iterator methods use `IDisposable`. Not a finalizer in sight. The question of how to make best use of `IDisposable` therefore has little to do with finalization (the use of which is typically strongly discouraged.) I'd also encourage anyone still doubtful about this to think of their favourite examples of unmanaged resources, consider how those resources manifests themselves to the programmer, i.e. how you interact with them in code, and say to yourself: what is it about this resource that makes it "unmanaged", in the sense intended by the MSDN doc page for `IDisposable`?
Daniel Earwicker
A resource is two operations: build-up and tear-down, open and close, enter and exit, allocate/free etc. where we have to somehow limit the number of outstanding invocations of the first operation without a corresponding invacation of the second operation ("instances" of the resource). If simultaneous instances can overlap their lifetimes in any way ("orthogonality") and the operations are threadsafe, and the state of the resource doesn't itself depend on finalizable objects, we could use finalization to manage the resource (it's a tall order though).
Daniel Earwicker
Um... not sure why but the link in the comment to a "complete discussion" was suppose to go to this: http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=88e62cdf-5919-4ac7-bc33-20c06ae539ae
Daniel Earwicker
+2  A: 

It's a hard one to pull off, but I've found the best way to get people to do something you want is to let them think it's their idea. You know them better than me, but phrases like "If only there was a way to find out why the objects are hanging around so long" and "I wish I knew more about events holding on to objects" might be a starting point.

harriyott
That's funny. For some reason it reminds me of Red Alert's "His Mind is Weak" phrase.
RichardOD
+2  A: 

Ask them if they would like to be forced to turn off the motor after using a bicycle, even if there isn't a motor.

Or if they would like to be forced to press a "turn off" button on their chair, desk, coffee cup and other stuff before their leave their workplace, even if there is nothing to be turned off.

Implementing IDisposable forces the user to explicitly tell the object when it is not used anymore. If this object does not need to clean up anything, it is just a unnecessary complexity.

By the way, IMHO unregistering events by implementing IDisposable is a suitable way to clean up events. There is something to "turn off".

Stefan Steinegger
+1  A: 

Propose a solution that is much superior than their solution ;-)

For example, a simple alternative could be to use WeakReferences in the long-living objects to hold the references to the event handlers. This would require that the event handlers are referenced somewhere else as long as they're needed, but as soon as they'd go out of scope, they're garbage collected and the weak references can be removed from the long-living objects.

dtb
Yes, but how would you use weak references in event handlers since the reference is built into the delegate?
http://blogs.msdn.com/greg_schechter/archive/2004/05/27/143605.aspx
dtb
A: 

In general, if you register a handler with an event, then "unregistering" is just the sort of basic cleanup every object should do upon being destroyed. If you don't have destructors in the language, then you have to define a method to call to tell the object it's going away. That method should clean up the event handlers.

Isn't this what IDisposable is for? Why would you need another solution if it's already there? And why didn't those knuckleheads implement their objects correctly in the first place? ;)

A. L. Flanagan