views:

133

answers:

5

Certainly we should call Dispose() on IDisposable objects as soon as we don't need them (which is often merely the scope of a "using" statement). If we don't take that precaution then bad things, from subtle to show-stopping, might happen.

But what about "the last moment" before process termination? If your IDisposables have not been explicitly disposed by that point in time, isn't it true that it no longer matters? I ask because unmanaged resources, beneath the CLR, are represented by kernel objects - and the win32 process termination will free all unmanaged resources / kernel objects anyway. Said differently, no resources will remain "leaked" after the process terminates (regardless if Dispose() was called on lingering IDisposables).

Can anyone think of a case where process termination would still leave a leaked resource, simply because Dispose() was not explicitly called on one or more IDisposables?

Please do not misunderstand this question: I am not trying to justify ignoring IDisposables. The question is just technical-theoretical.

EDIT: And what about mono running on Linux? Is process termination there just as "reliable" at cleaning up unmanaged "leaks?"

LATE EDIT: Although "other uses" may exist for IDisposables, my focus is strictly on resource leaks. I've heard two answers: (1) if your process refuses to terminate, you will have a leak and (2) yes, resources can leak even if the process terminates. I certainly agree with item (1), though it is just outside the scope of what I'm after. Otherwise, item (2) is exactly what i'm looking for, but I can shake the feeling it is just a guess. Jeffrey Richter ("Windows via C/C++") explains that a (gracefully) terminated Win32 process will not leave leaked or orphaned resources. Why would a process containing the CLR change that? Where is the documentation, specific example, or theoretical scenario that gives credance to the idea that the Win32 process cleanup capability is compromised by using the CLR?

+1  A: 

One thing that I often find myself disposing is serial ports-- now, while a serial port should be freed when the program lets up, other programs can't access the serial port while it's being held by another process. So, if your process refuses to die, then you're tying up a serial port. That can be really bad if the user tries to restart your program, but a zombie version of the previous process is still holding on to the serial port.

And yes, I've had my program find itself in a zombie state before, and then the customer complain that the program isn't working anymore because the program fails to attach to the serial port on restart. The result is either walking the user through killing a process in the task manager or having them reboot, neither of which is a particularly user-friendly task.

mmr
Don't use Invoke() in the DataReceived event handler, it is prone to deadlock the Close() call. Use BeginInvoke() instead.
Hans Passant
I'm pretty sure the zombification happens elsewhere; I don't use the DataReceived event handler because the device I'm attaching to is flaky and doesn't have a proper stop character. Instead, it sends a checksum of the command as the stop character, and since the checksum is always different, I have to build up the string a character at a time. It's excruciating, and clearly designed by someone in hardware, not software.
mmr
A: 

You write your custom code to free objects in your disposable class. It's for you to write code to free unmanaged and managed code.

This is why we need Dispose function. All freeing up of memory is not automatic, like you said in unmanaged code's case.

Now, if you think that unmanaged code is automatically freed by OS, it's not true. There are many handles and locks that may remain active if not properly disposed by your application.

Nayan
+1  A: 

Technically speaking, it all depends on what the IDisposable does. It has been used for a lot of things, not just unmanaged resources.

For example, when working on an Outlook application I had built a nice little abstraction of the Outlook API. Attachments were particularly annoying to work with as streams because you needed to save it out to a temp file, work with it, then clean it up.

So my abstraction looked something like this:

OutlookMailItem mailItem = blah;
using (Stream attachmentStream = mailItem.OpenAttachment("something.txt")) {
   // work with stream
}

When Dispose was called on the AttachmentStream, the temp file it was based on was deleted. In this case, if Dispose was not called, the temp file would never be cleaned up. I had a process at startup to look for these orphaned files but I figured I'd present this as an example.

In reality, nearly all IDisposable implementations which wrap some kind of socket, handle, or transaction will simply be cleaned up by the operating system upon process termination. But obviously that's like welfare. Avoid it if you can.

Josh Einstein
Agreed, ordinarily we should not rely on the welfare of the Win32 process termination cleanup. But you do seem to agree that Win32 process termination will free up any remaining resources that IDisposables *should* have handled. I'm beginning to think your post is my answer. Thoughts?
Brent Arias
There's really two parts to that question. The first involves the framework. It does not guarantee Dispose will be called but it makes a best attempt. The second part of your question is what happens if the finalizers are not run. And that's where "it depends". Windows has built in cleanup of handles and such. When the IDisposable wraps one of these resources then Windows will clean up regardless of what the GC did. But if the IDisposable does something weird (like my example of deleting a temp file) then it's possible that the cleanup will never occur.
Josh Einstein
A: 

First, I would like to point out that IDisposable is not only for Windows kernel objects or unmanaged memory. Rather, it is for things that the garbage collection does not understand (of which kernel objects are a special case).

A small example, just to give you an idea:

    sealed class Logger : IDisposable
    {
        private bool _disposed = false;

        public Logger()
        {
            System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STARTED" );
        }

        ~Logger()
        {
            Dispose();
        }

        public void Dispose()
        {
            if ( !_disposed )
            {
                System.IO.File.AppendAllText( @"C:\mylog.txt", "LOG STOPPED" );
                _disposed = true;
            }
        }

        public void WriteMessage( string msg )
        {
            System.IO.File.AppendAllText( @"C:\mylog.txt", "MESSAGE: " + msg );
        }
    }

Note that this Logger class does not "hold" any kernel-object-style resources. It opens file and closes it right away. However, there is this logic behind that it should write "LOG STOPPED" when the object is destroyed. This logic is something that GC cannot understand - and this is the place to use IDisposable.

Therefore, you cannot count on Windows cleaning up kernel objects after you. There might still be something even Windows doesn't know.

That being said, however, provided your disposable objects are properly written (i.e. calling Dispose in finalizer), they will still be disposed by the CLR upon process exit.

So, the answer is: yes, it is not necessary to call Dispose just before project exit. But NOT because disposable resources are kernel objects (because they're not necessarily are).


Edit

As Josh Einstein correctly pointed out, finalizers are actually not guaranteed to run on process exit. So then the answer becomes: always call Dispose, just to be sue

Fyodor Soikin
"they will still be disposed by the CLR upon process exit" Finalizers are not guaranteed to run at process termination and unless there are few of them and they finish quickly, it's very likely they will be interrupted if even called at all.
Josh Einstein
Found some supporting statements in addition to my comment above. The timeouts are longer than I thought but the CLR will still impose finalizer timeouts which would be particularly noticable if doing IO in the finalizer. http://nitoprograms.blogspot.com/2009/08/finalizers-at-process-exit.html
Josh Einstein
Yes, you're right, I completely forgot about that.
Fyodor Soikin
+1  A: 

During a cooperative shutdown, the AppDomain is unloaded, which causes all finalizers to execute:

From Object.Finalize documentation:

During shutdown of an application domain, Finalize is automatically called on objects that are not exempt from finalization, even those that are still accessible.

So you're safe on shutdown as long as two criteria are met:

  • Every IDisposable object that's still alive has a correctly-implemented finalizer (true of Framework classes, may not be true of less-trustworthy libraries); and

  • It's actually a cooperative shutdown, and not an abnormal shutdown such as a hard process termination, Ctrl-C in a console app, or Environment.FailFast.

If either of these two criteria aren't met, it is possible that your application is holding onto global unmanaged resources (such as a mutex), which actually will leak. Therefore, it's always better to call Dispose early if you can. Most of the time, you can rely on the CLR and object finalizers to do this work for you, but better to be safe than sorry.

Aaronaught
Just a caveat - finalizers are not guaranteed to be called during process termination. The CLR will make an attempt but it shouldn't be expected. The current behavior has time limits per finalizer and for overall termination. This post has some supporting statements: http://nitoprograms.blogspot.com/2009/08/finalizers-at-process-exit.html
Josh Einstein
@Josh: Very true. Of course, nothing is ever guaranteed; if you pull the plug, *nothing* is going to get cleaned up. ;)
Aaronaught
In addition to Josh's comment above, there are circumstances in which *no* finalizers will be invoked, such as invoking `System.Environment.FailFast` (this is basically the .Net equivalent of kill -9 on *nix).
Nathan Ernst
@Nathan: Thanks, but I mentioned that explicitly in my answer.
Aaronaught