What is this inconsistent heap state that MSDN warns me about during DLL_PROCESS_DETACH?


In the MSDN documentation for the Dll­Main entry point, MSDN notes:

When handling DLL_PROCESS_DETACH, a DLL should free resources such as heap memory only if the DLL is being unloaded dynamically (the lpReserved¹ parameter is NULL). If the process is terminating (the lpvReserved parameter is non-NULL), all threads in the process except the current thread either have exited already or have been explicitly terminated by a call to the Exit­Process function, which might leave some process resources such as heaps in an inconsistent state. In this case, it is not safe for the DLL to clean up the resources. Instead, the DLL should allow the operating system to reclaim the memory.

A customer wanted to know, "What is this inconsistent heap state that MSDN is talking about here?"

The information is actually right there in the sentence. "... explicitly terminated by a call to the Exit­Process function, which might leave some process resources such as heaps in an inconsistent state."

When you see text that says "X might lead to Y," then when you ask "What could lead to Y?" you might consider that it is X.

Background reading: Quick overview of how processes exit on Windows XP, plus bonus electrification of critical sections and slim reader/writer locks.

Okay, I'll give the customer the benefit of the doubt and assume that the question was more along the lines of "Why would termination by a call to the Exit­Process function lead to an inconsistent state?"

Remember why Terminate­Thread is a horrible idea: It terminates the thread in the middle of whatever it is doing, not giving it a chance to restore consistency or otherwise provide for an orderly end to operations. The thread may have been in the middle of updating a data structure, which usually involves perturbing an invariant, then re-establishing it. If it was terminated before it could re-establish the invariant, you now have an inconsistent data structure.

That's the sort of inconsistency the paragraph is talking about. If one of the threads terminated by Exit­Process was executing heap code at the moment it was terminated, then the heap may be inconsistent because the thread never got a chance to re-establish consistency. (The heap tries to detect that this has happened but all that does is transform one failure mode into another. The failure is still there.)

Of course, the heap isn't the only resource that suffers from this problem. Any resource that is available to more than one thread is susceptible to this. It's just that the heap is a very popular shared resource, so it gets an extra mention in the documentation.

¹ Typo. Should be lpvReserved.

Comments (23)
  1. Joshua says:

    Ah yes the oldest case of a boolean pointer. It might be entertaining to write a hundred bytes or so to disk at that point to see what it points too, but I can't imagine what there is to do with it. Can't do much w/ no heap anyway.

  2. Ishai says:

    So what is the point of calling the DLL entry point in this case?  Why doesn't Win32 just terminate the main thread too?

    [How would you implement C++ global destructors? -Raymond]
  3. Cesar says:

    @Joshua: I'd expect the pointer to point to something useful in the DLL_PROCESS_ATTACH or DLL_THREAD_ATTACH cases, and to have been repurposed as a boolean in the DLL_PROCESS_DETACH or DLL_THREAD_DETACH cases. Something internal like the PEB, the TEB, or the PE header.

    Wine seems to always either pass 0 or 1 as the pointer, so even if it ever pointed to something useful, there should not be many programs in the wild depending on it.

    Perhaps it's a Win16 leftover? I found a documentation for Win16's LibMain where the last parameter was lpszCmdLine. So my final guess would be: in DLL_PROCESS_ATTACH, when the DLL was imported directly by the executable, it was originally designed to point to the command line or something related to the command line, to ease porting from Win16; when the DLL was loaded dynamically, it's NULL. In all the other cases (thread attach and thread/process detach), it's free, so it was overloaded in the DLL_PROCESS_DETACH case to allow the DLLs to know when to free resources or not (as explained by Raymond in this post).

  4. Joshua says:

    @Cesar: Now that's almost Raymond quality.

  5. alegr1 says:

    [How would you implement C++ global destructors? -Raymond]

    It's not safe to call global destructors, too, because those objects can try to free some heap buffers, without knowing it's not safe.

    It would make sense, though, to have some global flag in HeapFree, set at ExitProcess call time, to skip actual heap freeing.

    [It's safe if your program is single-threaded, since no threads get terminated in that case. And for over a decade, C++ was a single-threaded language. -Raymond]
  6. Ken Hagan says:

    "[How would you implement C++ global destructors? -Raymond]"

    Since you couldn't generally depend on those destructors not doing dangerous stuff with the heap, perhaps you would decide that it is better not to call those destructors in this case. :)

  7. Myria says:

    I presume that the same applies to DLL_PROCESS_DETACH in TLS callbacks?

  8. Darran Rowe says:

    [It's safe if your program is single-threaded, since no threads get terminated in that case. And for over a decade, C++ was a single-threaded language. -Raymond]

    I also am in the habit of trying to make sure that all threads besides the main thread exits before the application itself closes. Obviously that isn't always possible though.

  9. Yuhong Bao says:

    @Cesar: AFAIK on NT it is a pointer to a CONTEXT structure, and sometimes it is declared as PCONTEXT.

  10. Cesar says:

    @Yuhong Bao: It seems you nailed it. Searching the web for "DllMain PCONTEXT" returns articles like this one: http://www.nynaeve.net/?p=127

    From that article: "Win9x is, of course, completely dissimilar as far as DllMain goes. None of this information applies there."

    …so yeah, it screams "implementation detail", as I had expected.

  11. Henri Hein says:

    @Ishai: We drew different lessons. You concluded "don't do anything in DllMain()." I concluded "Make sure your threads are done before exiting the process."  If you write a DLL and use threads, design a way to make the threads exit before DllMain gets called. Correctly implement DllCanUnloadNow(). Etc. (I once had a case where the ATL framework gave me the wrong result for DllCanUnloadNow() and I had to write my own, possibly because I did something wrong with ATL).

    [Hwo do you prevent someone from calling ExitProcess before your threads are finished? -Raymond]
  12. Joshua says:

    [Hwo do you prevent someone from calling ExitProcess before your threads are finished? -Raymond]

    API Documentation?

    [It's generally considered bad form for a DLL to make demands on when the process exits. (Say your DLL is being used as a plug-in in another process. That host process doesn't know your DLL's crazy rules. it's your responsibility to follow the host process's rules, not vice versa.) -Raymond]
  13. Henri Hein says:

    @Raymond: If I discover that a library I use calls ExitProcess, or any of its cousins, like abort() or TerminateProcess(), I will at the least rewrite my use of the library to prevent the code-path that triggers it.  At worst, I will happily take the hit to replace the library.  I feel that strongly about it.

    I realize that doesn't work when your product supports run-time configuration of plug-ins.  I haven't worked on a product that relied heavily on plug-ins, and when encountering such a plug-in, I have been known to ask CS to tell users with it to uninstall it.

    I also realize there are exceptional circumstances, such as a rarely seen exception.  Depending how often it occurs, I might have bigger priorities, but again, my preference would be to either rewrite code to avoid that exception from being thrown, or write an error-handler for it and exit cleanly, if possible.

    [I'm not talking about some random DLL calling ExitProcess. That's also rude. I'm talking about you, a DLL, a guest in someone else's house, creating additional rules for the house. You may not even have been invited to the house by the owner. Maybe the main process loaded a DLL, and that DLL in turn loads your DLL. How can the intermediate DLL communicate its restrictions back to the host process? -Raymond]
  14. Henri Hein says:

    @Raymond, after I see your response to Joshua, I realized you mean, how do I prevent someone from calling ExitProcess before my threads in my DLL is finished.  When I provide a DLL, I usually provide it through COM interfaces, and I rely on DllCanUnloadNow() to tell me when it's time to exit and let the host know it is safe to do so, as the name implies.  I don't know how safe that is, but I have generally have had good luck with it.  Also, such DLLs rarely has as much in-memory state that I'm concerned about losing (for instance, I don't remember the last time I have had any critical IO going on inside a DLL.)  

    [CoUninitalize will ask a DLL if it is okay to unload now, but the answer is a foregone conclusion. -Raymond]
  15. Henri Hein says:

    @Raymond: Yes, the call is a foregone conclusion, and I learned that the hard way, but I still get the chance to check on internal state and do my housekeeping.  By this time, any threads I started will have been told it's time to quit, so all I have to do is WaitFor…().  My worker threads tend to exit quickly when told to do so.

  16. > What is this inconsistent heap state that MSDN is talking about here?

    > When you see text that says "X might lead to Y," then when you ask "What could lead to Y?" you might consider that it is X.

    Your analogy does not seem to apply to the customer's question as you stated it.

  17. Joshua says:

    [it's your responsibility to follow the host process's rules, not vice versa.) -Raymond]

    Which has led to me on more than one occasion when using a DLL with different rules, to isolate it by spawning another EXE and marshaling calls to it. The general form of this rule is not being followed at all. Especially when some EXEs have the rule "Don't create windows" or "Don't start COM (because of the previous rule)".

  18. Azarien says:

    Why is it still called lpvReserved if its meaning is documented?

  19. Matt says:

    > [Hwo do you prevent someone from calling ExitProcess before your threads are finished? -Raymond]

    You don't. ExitProcess (and abort()) etc are all dangerous functions if the caller doesn't know that there is only one thread currently running. If you're writing a DLL, implement some kind of "detach" function if one is provided, and expect your EXE to call it before aborting your background threads.

    If the EXE doesn't have some interface whereby it calls your DLL to safely tell you that it's about to yank out your threads from under you via ExitProcess, then the fault for the deadlocks and memory corruptions that might ensue are the fault of the caller of ExitProcess, not the fault of the DLL that failed to safely recover from it.

  20. cheong00 says:

    I remember the API documentation of some webcam explicitly said that you have to call <their_DLL_name>_Cleanup() before exit or the cam will fail to initialize the next time you want to load it.

    Maybe when all else fails, just create separate function that allow the hosting process calls for cleanup?

    Generally if someone always fails the second time, the programmer should know they should check the documentation or ask.

  21. Zan Lynx' says:

    ExitProcess never does what I want. A while ago I changed everywhere I called it to TerminateProcess. Disabling locks and letting wild threads scribble over memory mapped data files… I read the justifications but I STILL think Microsoft was wrong to implement ExitProcess the way they did.

  22. Michael says:

    "[How would you implement C++ global destructors? -Raymond]

    […. And for over a decade, C++ was a single-threaded language. -Raymond]"

    Do you happen to have any topics queued up covering thread_local constructors and destructors in VS2015? Aside from the obvious parts of "don't do anything exciting, because it's still basically DllMain".

    [I think you meant to direct this question to Stephan T. Lavavej. -Raymond]
  23. Michael says:

    Probably, but the parts I'm interested in are more OS-interaction specific, and those seem to not be in his domain of expertise. You can't really point to a section in the standard for what happens to thread_local variables in a DLL when the DLL is dynamically unloaded. But, thank you.

Comments are closed.

Skip to main content