Clean-up functions can’t fail because, well, how do you clean up from a failed clean-up?


Commenter Matt asks how you're supposed to handle failures in functions like fclose or CloseHandle. Obviously, you can't. If a clean-up function fails, there's not much you can do because, well, how do you clean up from a failed clean-up?

These clean-up functions fall into the category of "Must not fail for reasons beyond the program's control." If a program tries to close a file and it gets an error back, what can it do? Practically speaking, nothing. The only way a clean-up function can fail is if the program fundamentally screws up, say by attempting to close something that was never open or otherwise passing an invalid parameter. It's not like a program can try to close the handle again (or worse go into loop closing the handle repeatedly until it finally closes).

Remember this when writing your own clean-up functions. Assuming the parameters are valid, a clean-up function must succeed.

(I will now ruin my rhetorical flourish by yammering about the fclose function because if I don't, people will bring it up in the comments anyway. The fclose function does extra work before closing, and that extra work may indeed run into problems, but the important thing is that when fclose returns, the stream is well and truly closed.)

Addendum: Once again I wish to emphasize that while it may be possible for functions like fclose to run into errors while they are closing the stream, the point is that the result of the call to fclose is always a closed stream. I think most of the comments are losing sight of the point of my article and rat-holding on the various ways fclose can run into errors. That's not the same as failing. It never fails; it always succeeds, but possibly with errors.

Comments (29)
  1. John says:

    So why does CloseHandle() bother to have a return value?  It sounds like the only failure it can ever encounter is an invalid parameter, so either the object will be successfully closed or you passed an invalid parameter.  Either way, there is no further action for your program to take.

  2. anon says:

    @john: Debugging? So that you can tell from traces/logs that you’re doing the wrong thing sometimes?

  3. Jim Lyon says:

    So why does CloseHandle() bother to have a return value?

    To let you know that you the programmer screwed up, and give you a chance to fix it before it bites your customers.

    So what should your program do when it gets an error from CloseHandle? Log the error and die. Die suddenly, horribly, without any opportunity to save files or cleanup. This will force you to fix it.

    If you ship code that receives errors from CloseHandle(), occasionally you’ll pass it a handle that has been reassigned to some other component in your process, and you’ll be in the boat of trying to diagnose random failures. ("The framistat function got an InvalidHandle error, but I can see that it successfully opened the handle only 2 lines of code earlier.")

    — jimbo

  4. lf says:

    "The fclose() function does not handle NULL arguments; they will result in a segmentation violation.  This is intentional – it makes it easier to make sure programs written under FreeBSD are bug free.  This behaviour is an implementation detail, and programs should not rely upon it."

  5. Inode Jones says:

    fclose() and UNIX close() return error codes because errors may indeed otherwise go undetected.

    On Unix, an NFS client is permitted to cache write data locally.  It must force that data out to the server when the file is closed, and guarantee to the caller that the data has been written successfully.  If the server crashes, or gets a disk full, etc. then close() will fail.

    If close() fails, there’s not much you can do with THAT handle, but if the data you wrote is still available you can at least inform the user that the document save failed, and ask for an alternate path to save to, hopefully to a local disk that will work.

  6. jeffdav says:

    Yes, for debugging.  In fact, I’m a believer in ASSERTing that these types of functions succeed so you know right away if you have a bug.

    if (SUCCEEDED(OpenSomething(&thing)))

    {

       … your code here …

       SHOULD_NOT_FAIL_HR(CloseSomething(&thing));

    }

  7. Peter Ritchie says:

    @John, it needs a return value so you know to tear down the application…

  8. Xepol says:

    Ever seen a Delphi app throw an exception in a form’s OnClose method?  The form never closes, the app can’t close and you have to force it down.

    Seriously annoying and agrevating and a PERFECT example of trying to how trying to handle errors on cleanup can cause more problems than it solves.

    JGSoft’s EditpadPro suffers from this problem – it tries to write to the registry when it closes, if it fails, the form will never go away.  Just to make everything extra helpful, they catch the exception at the root application level and supress reporting, so you have no clue why your editor will not go away unless you know that it is written in Delphi and a lot about how Delphi works.

  9. So suppose you open a file that’s on a network drive and cache the handle…

    And then sometime after your last WriteFile(…) the network drops out…

    Will CloseHandle() return an error?  I sure hope so.

    Q: How do you clean up from a failed clean-up?

    A: Well, how do you clean up from a failed anything else?  On debug builds, you can ASSERT.  If you have a log available, you can log (unless the failure breaks logging.)  If you have UI you can present the failure to the user.  If you’re a running test case you can fail the test case.

  10. Phaeron says:

    To expand on Inode Jones’ point, the return value of fclose() should absolutely be checked and handled when writing a file — even when writing to a local drive. fclose() does an implicit flush on the stream buffer and if the final write fails, such as due to insufficient disk space, an error code will result. Ignoring the return code and terminating the application are not good ways to handle this.

    CloseHandle() might be a bit less likely to fail here, as the OS has already seen all writes from the application by that point, but it couldn’t hurt to handle it gracefully as well.

    [My point is that when fclose even “fails”, the failure mode is not “leave the stream open”. I tried to forestall this tangent in my final paragraph but once again I was unsuccessful. -Raymond]
  11. Sean W. says:

    I think this poses an fundamental philosophical design question:  Which is worse in a function that encounters an unrepairable error:  To tell the programmer about an error he can do nothing about, or to tell him nothing and pretend it didn’t happen?

    The program is probably toast either way, so it’s really like asking whether you want to be killed by an in-your-face firing squad or by the sniper’s bullet you never see.

    If you tell him about the error, he runs the risk of trying to fix it and making things worse, but at least he knows something went wrong.

    If you tell him nothing, he runs the risk of not knowing that something went wrong; but on the other hand, it’s impossible for him to screw up the error-handling since there are no errors to handle.

    Personally, my attitude when designing functions like these is to return void, because if the programmer can’t do anything about it anyway, telling him the error is more likely to hurt him than help him, since anything he’s likely to do at this point will just make the problem worse — of course, most programs ignore the return value from cleanup functions anyway.  My attitude stems from the error-reporting philosophy Al Cooper suggested in About Face:  Users don’t understand and don’t like errors, so when in doubt, don’t report anything you can fix or avoid.  Programmers don’t really understand or like errors either, despite how often we deal with them:  Most higher-level language designs know this and silently fix mistakes for you rather than report them because that’s easier to work with than the "report everything" philosophy of languages like C.

    So the less error-prone the design, the better, I think:  You may be losing a little information, but it’s information that was only marginally useful anyway, and information that’s more likely to cause problems in the long run than solve them.

  12. DriverDude says:

    "So what should your program do when it gets an error from CloseHandle? Log the error and die. Die suddenly, horribly, without any opportunity to save files or cleanup. This will force you to fix it."

    In today’s plug-n-play and wireless world, where all sorts of user blunders and transisant errors can occur, graceful error handling is a necessity. The user can unplug a disk drive after the app is done calling ReadFile/WriteFile but just before the app CloseHandle. If CloseHandle returns an error, should the app die horribly?

    It sure complicates debugging, but nobody said PnP and power management and error handling was easy. There are other ways to find logic errors without depending on the OS to catch them for you (the programmer)

  13. Jim A says:

    In general, I’m not a big fan of the MFC VERIFY macro, but I use it on my calls to CloseHandle type of functions, so I am at least checking the return value on a debug build during development. A call to CloseHandle that was failing was the clue to solving one of the nastiest bugs I had to deal with. The team was puzzled for months on this one.

  14. Dan says:

    A successful return from WriteFile does not guarantee that your data made it to stable storage.  It means that the write will be visible to subsequent reads, and that the OS will attempt to write the data to storage in the background.  If your app needs a stronger guarantee, then it should call FlushFileBuffers, or use unbuffered I/O and write-through.

    After all, what would your app do if the power went out after the last successful WriteFile, but before calling CloseHandle?  You still wouldn’t know if the data made it to the disk, so you can’t really assume that it did.

  15. Eugene says:

    "It never fails; it always succeeds, but possibly with errors."

    This goes into the quotation file ;-)

  16. fschwiet says:

    Maybe the modern view of this is, don’t throw exceptions from destructors or exception handlers.

    Really I’m afraid to do anything from an exception handler.  Write to a log file?  Good idea I hope the logfile doesn’t blow up.

    I pity the guys at Microsoft, you know someone there had to write some code "throw new OutOfMemoryException()" and had the darndest time getting it to work.

  17. Vanilla man! says:

    Surely, in the context of some executing program there are different classes of fault.

    Do people really provide functionality to act upon every single error status?

    So, you have piece of code X, a subset of monster program Y. X calls "close" which returns an error status. Now, sometimes Y crashes horribly. On one occasion, I spotted something wrong, and it lead me to "close" after which the whole of Y was clearly failing.

    When I tried to recreate the problem I couldn’t, but I know that after close nothing worked properly.

    Well, I have to get back to Z (another subset) now but I’d really like to know if "close" fails, because I might be able to get a better handle on the context prior to close.

    In fact what I’ll do is put in a debug message, which only prints when close fails, and it gives me some information related to the things I suspect could be a problem.

    Personally, unless I’m expecting that something could fail (file open) any kind of response to a system function error code, is debugging, and ripe for elimination, however,

    where I come from you arent allowed to remove any code until you know what it does. Equally you’re neglecting your duty as a good programmer if you only ever add code!

  18. Worf says:

    What Raymond is saying here that should CloseHandle() or fclose() or close() return any error at all, the only thing guaranteed is the OS does not have whatever that handle/file/file object represents open anymore. All uses of the handle, FILE*, file descriptor will fail afterwards.

    However, when you get an error, you can see if there are alternative ways to fix the problem.

    If CloseHandle() on a COM port fails – can’t so anything, ignore failure. If CloseHandle() on a library object (LoadLibrary()) fails, ignore it. (You can log it, or MessageBox() it). You can’t do anything with the error, so it’s best ignored, logged, or disregarded.

    BUT, if it fails closing a data file the user has open, you have options! Whatever the failure, you can’t assume that the user’s data made it to storage. However, users like to know their data is safe, so handle it in a safe way.

    If you opened a file for reading, well, nothing needs to be done – you read in everything you were going to read (hence you close the file), so it doesn’t matter that storage disappeared. The only thing you should note is if you’re going to reopen the file for writing, perhaps it’s not a good idea – the close returned an error, so the file may not exist or be writable anymore. Do something about that – try another path, save a recovery file, anything.

    With writes, well, you can’t assume that the file caches were flushed from the last write if there’s an error, so it’s possible the file is now corrupt. Hopefully there’s enough state information around that you can either save another copy, write it to a journal to replay should the original file reappear, do something. The fact that you couldn’t close the file should mean that the user’s data is now in jeopardy because it may not exist on disk, network, whatever (if it’s in an inconsistent state, for example). The only representation is in RAM.

    It also depends on the type of file you’re working with. Let’s take a video editing suite. If you’re rendering the final video given the cutlist, and the final video cannot be closed, you have the cutlist, and can always re-do the video render. If you’ve failed in closing the cutlist, then perhaps you should do something since that’s the real data that work has been put into creating. Try the save elsewhere. Make a backup file somewhere. If all else fails, ask the user "I could not guarantee your data was saved, do you wish to try to save somewhere else?"

    Basically, the mantra is, if you can recover, or you failed closing a user file, try to find another place to put it. If it’s something that you can’t fix yourself, or if it doesn’t matter because it can be re-done, it’s best to ignore the error (e.g., you can’t help it if you can’t close the serial port, or a DLL, or a GDI object, so move on.)

  19. Mike Dimmick says:

    @Dan: it’s worse than that. NTFS (unless using Transactional NTFS in Vista/Server 2008) only guarantees that its metadata is in a recoverable state. It does not guarantee that *your data* made it to disk even after you closed the handle.

    Even FlushFileBuffers might fail if you have an IDE drive which lies to the OS about completing the I/O (which most do – they write to their internal buffer and return, then lazily flush the buffer). The difference between ‘desktop’ and ‘enterprise’ SATA drives is that the ‘enterprise’ drives actually honour the write-through flags. They also don’t do such rigorous error recovery – a ‘desktop’ drive will keep going over a bad sector repeatedly trying to recover the data; the ‘enterprise’ drive assumes it’s part of an array, the filesystem can recover the data from another disk, so fails fast so the OS can get on with fixing the problem.

  20. Neil says:

    In other words, if your application requires CloseHandle to succeed, then you’ve already lost.

  21. Coleman says:

    "…the point is that the result of the call to fclose is always a closed stream."

    Right.  But, what do you do when it returns an error?  Great; the stream is closed, but how should the program proceed, in your opinion?  I disagree that it should fail in a loud, grotesque, military manner.  But, it should do *something* gracefully; something at a minimum being notify the user (log file, debug string, whatever).  

    The application should NOT go on assuming that everything is OK.  In the above example of unplugging the drive, I would expect a popup dialog indicating a serious, unrecoverable error.  If fclose fails^H^H^H^H^H succeeds with an error, there are more serious problems ahead.

    On another note: "…don’t report anything you can fix or avoid."  So, the application shouldn’t report an error if malloc or new fails?  To me, it’s not about telling the programmer, it’s about telling the user who could very well do something about it.  Out of memory?  Close some other application.  Don’t just fail, tell the user *why* you’re failing.  Otherwise, you’ll be getting the email that says "the application I bought from you doesn’t work" with no more information to help you troubleshoot the issue.

  22. eran says:

    I know that CloseHandle ultimately goes into the kernel where it create at least two IRPs (CLEANUP and CLOSE), those operations can fail (At least the IRP allocation). Cannot CloseHandle fail because of resource issues in kernel mode?

  23. Tim Smith says:

    Ok, so fclose has failed, what are you going to do about it?  Sure you can detect the failure but to what end?  How much code are you going to write to support "proper" error handling for something that might happen once in 100 years for a standard application?  How are you going to test this code?

  24. Thom says:

    @Tim – It depends on the application.

    Suppose it’s a word processor and one of the scenarios above exist causing CloseHandle to fail during an attempt to save a document.  Ignore it and a portion, or possibly all, of the document is lost – possibly without the user knowing it.  Ooops there goes that big presentation tomorrow.

    Why would you die horribly or ignore the error when a sane, and very simple, very testable, solution would be to not clear the dirty flag for the document and to tell the user that an error occurred during the save.  That way the user couldn’t inadvertantly close the document and lose their changes.  They could attempt the save again, at which time the might be told the network was disconnected or the USB device was unattached or unavaiable so they could fix it and resume, or they could attempt to save the file to a different drive.  

    As long as the app logic was sane and didn’t try to use this same handle but started the save process anew, you’d very likely find a way to save your work.

  25. Jim Lyon says:

    From experience, I can tell you that CloseHandle() does not give an error if attempts to flush data you’ve written fail. In fact, CloseHandle() doesn’t even attempt to synchronously flush your data, so it may not get flushed until minutes later, after your program is long gone.

    From reading one version of Windows source, it looks like CloseHandle can fail for only two reasons: the handle doesn’t exist, and the handle is marked as protected from being closed.

    In other words, a failure in CloseHandle in particular means that you the programmer screwed up, not that there is something wrong in your environment.

    In general, error handling is hard. I find it helpful to split errors into three categories:

    1. Expected errors. If you’re calling DeleteFile with a mindset of "I don’t care if the file currently exists; I just want it gone," then FILE_NOT_FOUND and possibly PATH_NOT_FOUND are expected errors, and are easy to deal with.
    2. Environmental errors. These are the ones where your request is reasonable, but something over which you have no control has gone wrong. Network failures are one classic example.

    3. Programer errors. These are errors that indicate that the programmer did something wrong. In addition to CloseHandle, errors from APIs like SetEvent and ClearEvent tend to fall into this category.

    My main point was that the appropriate response to errors in the 3rd category is usually to fail fast. People like DriverDude above point out that this is not necessarily the right answer for the second category; I agree.

  26. pete says:

    Any pervasive, consistent approach to runtime errors is better than none at all. When fclose returns an error, I would rather see a program over-react than ignore it. At least then you know something bad happened and can cope, even though it may be possible to cope only by updating deployed software. Even that is better than blundering forward ignorantly. It takes a leap of faith to actually put this into practice, but once you’ve done it in a non-trivial project, you will never go back. You will find so many bugs in your own code before they turn into monster debugging sessions that you’ll wonder how you ever survived before.

  27. Bryce Maryott says:

    Saying that cleanup functions shouldn’t return a value level (which is really all we’re talking about) is close to saying no running programs should return such a value either.

    Consider this: fClose can fail for a multitude of reasons – coding mistakes, hardware problems, user intervention, etc.  The more information that can be passed back up the program tree, the more effective you can deal with the issue.  Not all such problems can be give to the user, but some can and should ("Unable to access USB drive – you’ve probably corrupted the file by yanking it out without stopping it again!") and some should be logged ("network file unable to close correctly – check integrity or rebuild when the program restarts!") and some can be ignored ("// Third day working with no sleep – will need to double check I need this in the morning. 04:29 04-02-1996")

    But the REAL reason your cleanup functions should be ready to return odd conditions gracefully (Killing everyone in the room is NOT graceful!) is because you’re not the only programmer in the world.  99.99% of the time, there will be someone else that has to work on your code, including the cleanup functions.  Odds are good they’re not going to know what you did or why, nor are they able to real throught the 12 billion lines else where (including libraries that may not have the origional source handy!) and the if someone comments out a section that initates something your cleanup is designed to handle and breaks it, you should be the option to decide what to do.  (1) fclose fails and I kill the computer, the user and his first born.   (2) fclose fails and I ignore it.

    If a function fails (and a cleanup really is JUST a function!) and you don’t pass that information along, I ask that you don’t program libraries.  Because you don’t know what kind of code I’m writing, let alone the way I’m coding.

  28. Thom says:

    Let’s not forget that future changes to the OS could result in additional intricacies by which functions such as CloseHandle() could fail.  Be it due to new classes of storage devices, new file systems underlying the OS, or other means there’s always the possibility that some subtle change will allow CloseHandle() to  fail for reasons different from those it fails for today.  If you were checking that return value and acting appropriate to your application then you’ll either be covered automatically or you’ll have a much easier time identifying the source of the failure.  If you’re not… have fun debugging.

  29. Flawed logic says:

    In other words, if your application requires CloseHandle to succeed, then you’ve already lost.

    Yes. But it’s a difference between showing a error message and crashing/writing corrupt data.

Comments are closed.

Skip to main content