Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Myths about finalizers, part two (ericlippert.com)
48 points by AndrewDucker on May 21, 2015 | hide | past | favorite | 20 comments


Garbage collection is a leaky abstraction and finalizers are the edge where it leaks.

I tend to think that using finalizers is almost always a bad idea. They're just too complicated: there's no way you can reason about them effectively. This long list of assumptions you can't make about finalizers is just an example of this: if you have to keep all of this in mind you're forgetting something else. Luckily there are alternatives.


That's what Eric Lippert's trying to get at here, in tortuously brutal detail. Here's the last line of the post again:

  It is therefore very difficult indeed to write a correct
  finalizer, and the best advice I can give you is to not try.
Since the initial release, .NET has come up with additional mechanisms to try and minimize the number of situations where you would need to implement a finalizer. At this point I find that it's always possible to avoid one. When I think I need one, I ask myself the following question: "Am I hacking on the core framework?" If the answer is no, then I probably shouldn't be writing a finalizer.


Well, it always seemed to me that the one good use for a finalizer was to make sure that the coder remembered to call the Dispose() method...

AKA say you have a resource (like a file handle) that is being wrapped by a class. In C++ the constructor would open the file, and the destructor would close it. In a GC'ed environment because you can't determine when the instance is going to get cleaned up the programmer ends up calling a .close() or Dispose() method instead of the destructor. So the finalizer ends up just being a little piece of code that says, did the user remember to call close? No? Complain loudly enough someone will fix it.


The preferable options in these sorts of situations generally boil down to:

  1. Use a SafeHandle or CriticalHandle
  2. Let the finalizers for the resources you're holding take care of themselves.

#2 because if your resources already have their own finalizers (e.g., streams, db connections), then implementing your own is like a belt and suspenders. Except that whichever one you added 2nd is probably broken and makes everything strictly worse.


Sure, there are other strategies for handling the problem. But many have latent bugs, which is mostly what this article is pointing out.

In your case 2, unless you try to force the finalizer to run (he has a collect/waitforpendingfinalizers sequence in one of his examples) this potentially leaves the resource consumed for an indeterminate amount of time.

Its also not clear to me that the *Handle routines guarantee correct behavior with processes running outside of the CLR. I just looked at the docs and it reenforced my understanding that they seem to be strictly designed to protect .net applications from shooting themselves in the foot. I'm not a .net/CLR expert so take that for what its worth.


I think this is pretty much the reason most people think is a good reason to use a finalizer, but it's also the case that inevitably comes back to bite you.

I've made this mistake, and the way it bit me was in a system where I was spinning up and disposing a lot of event subscriptions. My UI slowed to a crawl. It turned out that the finalizer queue is pretty low priority, so event subscriptions weren’t getting disposed by the finalizer until there was time to do so. As a result, extra event handlers were being notified (and doing nothing because they in turn called other events which were no longer subscribed). This wouldn’t have been a problem to find and fix, except that I had written the finalizer six months before and one of our junior devs had written code that depended on Dispose() being called by the finalizer instead of explicitly. The result was that profiling showed a lot of time being spent on meaningless code that wasn’t actually related to the problem. It took almost a week to figure out the issue. I thought when I was writing that finalizer that I was writing fail-safe code, but in reality I had just made the failure harder to trace.

In short, the way to ensure that Dispose() has been called is to call Dispose() at the appropriate time. There’s no shortcut, you have to just manage the lifetime of your objects correctly.


I wasn't suggesting calling the dispose method in the finalizer (that is evil), rather setting a flag in the dispose method that indicates whether it was called. Then logging a message/whatever in the finalizer if that flag isn't set.

AKA, using it as a debugging aid.


Have you read the article?

"it is possible that the finalizer and constructor are running concurrently."


Well, Yah if you didn't use it.. But AFAIU the point is that it won't happen if you have to call a dispose method (the dispose operations would block the finalizer)...

AKA, if you have an object that needs to be disposed, and the finalizer is running concurrently with the constructor then that is because you forgot to call the Dispose() method and any complaining it does is valid.

EDIT: Although, thinking about it more, the bigger problem is the partial construction case, where you need to be really careful how you set/unset the flag used to determine if the dispose method is called. But if something is bad enough to result in the construction phase failing in some really bad way, then there should be noise about that too. I would hope!


Let's say you have a Lock object that wraps a pthread_mutex_t. How do you expect clients to determine when they should Dispose it?

The problem with Dispose is that it's infectious. If one of your instance variables needs to be Dispose'd, then you do too. This is all sorts of bad: it violates LSP, leaks implementation details, adds compatibility constraints, etc.


I totally agree. This is one of a number of concepts that keep me away from most GC'ed languages.

BTW: Even in C++ though, I wouldn't use a pthread mutex directly, or in a wrapper class if I haven't banned exceptions. The "correct" way is to create a resource wrapper class without public lock/unlock calls and then a second RAII lock class that can lock/unlock the wrapper. Then if an exception happens while holding a lock you at least have a chance of properly unlocking it, or noticing that it wasn't unlocked.


Reference counting?

The second line is why GC is a lot of hype... eventually you need a pthread_mutex_t and GC programming becomes just as onerous as reference counting.


This perspective was best expressed by Raymond Chen (here: http://blogs.msdn.com/b/oldnewthing/archive/2010/08/13/10049... ):

> Finalizers are a Ouija board, permitting dead objects to operate "from beyond the grave" and affect live objects. As a result, when finalizers are involved, there is a lot of creepy spooky juju going on, and you need to tread very carefully, or your soul will become cursed.


The only situation where I can't find a way to avoid them is handing out managed objects as COM to unmanaged clients.


Yes, always manage objects explicitly.

If we can't use scope locks, we should use open()/close() -style semantics.


If you're working with .net code that uses finalizers (especially if those sections were written by people unlikely to read articles like this, though good programmers screw this up too), chances are that there are latent issues in your application that will manifest as sudden application crashes with little to no debugging information.

I found myself in such a situation. Books like Advanced .NET Debugging [1] will help you regain your sanity.

[1] http://www.amazon.com/Advanced-NET-Debugging-Mario-Hewardt/d...


TBH if you need to do advanced debugging, it might be better to just not use finalizers.


I've never had to use finalizers in java. When is this functionality actually needed? Any examples when it's used. In java finalizers may not be run at all afaik.


I haven't yet come across a case where it's actually needed, but I've come across a few cases where it seems like the easiest solution.


Thankfully the worst of those problems is the easiest to deal with. Put a call to GC.KeepAlive(this) at the end of your constructor. At least then you'll have some sanity in the case that the finalizer runs.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: