Once you return from the WM_ENDSESSION message, your process can be terminated at any time


A customer had a program which performed some final I/O operations as it exited. Various C++ objects deleted files or flushed buffers as part of their destructors. The customer found that if their program was left running when the user shut down Windows, then the files never got deleted, and the buffers were never flushed. On the other hand, if they inserted an artificial delay into the shutdown procedure, so that it waited ten seconds after the program exited before continuing with shutdown, then the files did indeed get cleaned up and the buffers were indeed flushed. The customer confirmed that the program did receive the WM_END­SESSION message, but it appeared as if all disk I/O issued within five seconds of shutdown never gets committed to disk. This would appear to be a serious bug in Windows.

Because, of course, when you find a problem with your program, your first reaction should be to assume that you found a bug in Windows so blatant it should be affecting every program on the planet, and yet somehow this horrific data loss bug eluded not only the entirety of the Windows QA team, but also every software developer for the past twenty years who had a program that saved data at shutdown.

Or the problem could be in your code.

The documentation for the WM_END­SESSION message says,

wParam
If the session is being ended, this parameter is TRUE; the session can end any time after all applications have returned from processing this message.

What is much more likely to be happening is that when the application receives the WM_END­SESSION message, it posts a message to itself to initiate controlled shutdown. After the program returns from the WM_END­SESSION message, the message pump picks up the shutdown message and it is at this point that the program starts cleaning up objects, including running destructors and flushing buffers, and then finally calling Exit­Process.

In other words, the problem is not that the final I/O never got committed to disk. The problem is that the final I/O was never issued by the program. Once your program returns from the WM_END­SESSION message, Windows has the right to terminate it without further warning. If your system shuts down quickly, that termination may occur before your destructors manage to run at all.

You cannot rely on any code in your program running once you have responded to the WM_END­SESSION message. That message is your "final warning". If you need to do cleanup operations before termination, you need to do them before returning from the WM_END­SESSION message. Because once you return from that message, your process is living on borrowed time.

Comments (21)
  1. alegr1 says:

    Now, if only Windows Vista+ haven't made WM_QUERYENDSESSION meaningless…

  2. @alegr1: Because there's nothing I like better than to have a program I can't interact with preventing me from shutting the PC down.

  3. Adam Rosenfield says:

    The application developer says, "It must be a bug in the library."  The library developer says, "It must be a bug in the kernel."  The kernel developer says, "It must be a bug in the hardware."  The hardware developer says, "[Expletive], it must be a bug in physics."

  4. Medinoc says:

    This mean you can't have global objects which perform important stuff in their destructors, doesn't it? Or else the only way to clean up is to call The CRT exit() without ever responding to the WM_ENDSESSION message…

  5. deiruch says:

    Well, the documentation could be clearer. It doesn't state that the process will just be terminated. Instead of

    the session can end any time after all applications have returned from processing this message.

    the documentation could state

    the current process may be terminated any time after all applications have returned from processing this message.

    to make it more obvious what's happening. But that's just, like, my opinion, man.

    [The missing dot to be connected is that when a session ends, all processes in it are implicitly terminated. The statement in the documentation is broader because it covers the case where somebody hands work off to some other process in the same session. -Raymond]
  6. 640k says:

    @Medinoc: Yes, global variables, either simple types or complex objects, is BAD programming. You're doing it WRONG.

  7. 640k says:

    @deiruch: Loss of power can happen at all times. There's not a "safe" moment in an applications executing lifetime.

  8. alegr1 says:

    Saving state only after exiting the message loop was why Visual Studio.NET was not saving last project if it was closed by the logoff. And also why Explorer in earlier Windows was not saving states in logoff.

  9. Simon Farnsworth says:

    @Adam Rosenfield

    In my experience, it's an endless loop:

    The application developer says "It must be a bug in the library."

    The library developer says "It must be a bug in the kernel."

    The kernel developer says "It must be a bug in the hardware."

    The hardware developer says, "It must be a bug in the application."

    Rinse, wash, repeat, until you get app, library, kernel and hardware developers together to stop the blame game.

  10. JM says:

    This might be a good hook to promote crash-only software (http://www.usenix.org/…/candea_html): assume your program will end by someone calling TerminateProcess() and design transaction processing and recovery accordingly. Do not bother with any complicated shutdown logic, because you have to handle the case where it never gets to run anyway — bother with robust startup recovery instead. This model isn't always appropriate, but where it is it can be surprisingly powerful.

  11. AsmGuru62 says:

    There is nothing wrong with global variables.

    Just new/delete them at proper times (create Startup/ShutDown routine(s) and call them as needed) instead of just declaring them.

  12. JM says:

    @Myria: I can see two obvious problems with that.

    First, correct shutdown code is often harder than other code because all sorts of previously safe assumptions are being invalidated left and right, in nondeterministic order if you're unlucky. If your shutdown code itself is buggy, then you have to fix it, but this does nothing to improve the quality of actually useful work our program performs. Applying a good programming regime like localized destructors and minimizing dependencies can reduce the complexity, but it's still a code burden. By contrast, validating your runtime state while you're still sure that it's all there is a lot easier.

    Second, you're detecting bugs at a time when we don't care anymore. If the bugs affected the program's execution, well, the damage has been done when it was still doing work. If they didn't, then either they're not bugs, or they're leaks that only affect other programs and we're shutting down to solve them. Of course, these objections disappear when you're in a debugging session exactly to establish if the program would exhibit instability or resource leaks over longer periods of time, so certainly this can be useful, but if you care about this sort of thing it probably pays off more to invest in piecewise validation at runtime and less in having the shutdown phase double as a leak detector or consistency checker. Since shutdown code isn't usually designed for that, the best it can do is tell you there is possibly a problem somewhere, then leave you all alone in the dark.

  13. Myria says:

    @JM: I agree with that.  Assume that your program can go *poof* at any moment, and preserve important state accordingly.

    The reason I see for keeping a controlled shutdown sequence in such software is that it helps you find bugs.  Often, your code will crash at shutdown because there was a mistake made while running.  Shutting down goes through your object tree and implicitly validates a lot of the runtime state.

    I generally work on programs that don't care about WM_ENDSESSION, because sudden disappearing from existence is nonfatal.

  14. henke37 says:

    Just stick a call to TerminateProcess in the message handler, problem solved.

    [You think you're joking but you're not. -Raymond]
  15. asdbsd says:

    @640k:

    Stop assaulting global variables, people. They're fine. From a purist point of view, in a functional world they may be ugly but passing everything by params and writing five level deep qualifiers is uglier.

  16. Wak says:

    […]Because, of course, when you find a problem with your program, your first reaction should be to assume that you found a bug in Windows so blatant it should be affecting every program on the planet[…]

    Not every program on the planet runs on Windows.

    [You must be a lot of fun at parties. s/every program/every Windows program/. While you're at it, /your program/your Windows program/. (You might be surprised that this Web site deals primarily with Windows programming, and consequently the scope of most qualifiers is implied.) -Raymond]
  17. JM says:

    @Wak: good point. Your first reaction should be to assume that you found a bug in the foundations of computing. "Turing complete" my ass.

  18. @Wak: Raymond's not allowed to exaggerate for dramatic effect? ;)

  19. alegr1 says:

    >Because, of course, when you find a problem with your program, your first reaction should be to assume that you found a bug in Windows so blatant it should be affecting every program on the planet

    In kernel programming, I encounter Windows bugs pretty often. Most not so severe, but some cause BSODs. Sometimes I can work around that, sometimes, it requires a QFE.

  20. Ian Boyd says:

    And as we learned last time: stop trying to cleanup resources or free memory when your application shuts down.

    blogs.msdn.com/…/10253268.aspx

  21. Mike Dimmick says:

    I discovered in the past that it's a bad idea to use C++ globals with a destructor in a DLL. The destructor runs, effectively, after the DLL_PROCESS_DETACH notification in DllMain (it runs in the *real* DllMain, DllMainCRTStartup, which the compiler inserts for you and calls your DllMain). That means that the destructor runs under the loader lock…

Comments are closed.

Skip to main content