The old-fashioned theory on how processes exit

Date:May 2, 2007 / year-entry #154
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20070502-00/?p=27023
Comments:    27
Summary:Life was simpler back in the old days. Back in the old days, processes were believed to be in control of their threads. You can see this in the "old fashioned" way of exiting a process, namely by exiting all the threads. This method works only if the process knows about all the threads running...

Life was simpler back in the old days.

Back in the old days, processes were believed to be in control of their threads. You can see this in the "old fashioned" way of exiting a process, namely by exiting all the threads. This method works only if the process knows about all the threads running in it and can get each one to clean up when it's time for the process to exit.

In other words, the old-fashioned theory was that when a process wanted to exit, it would do something like this:

  • Tell all its threads that the show is over.
  • Wait for each of those threads to finish up.
  • Exit the main thread (which therefore exits the process).

Of course, that was before the introduction of programming constructions that created threads that the main program didn't know about and therefore had no control over. Things like the thread pool, RPC worker threads, and DLLs that create worker threads (something still not well-understood even today).

The world today is very different. Next time, we'll look at how this simple view of processes and threads affects the design of how processes exit.

Still, you learned enough today to be able to solve this person's problem.


Comments (27)
  1. jachymko says:

    Adam: Just thinking, how would threads created by another process (using CreateRemoteThread) know they should quit?

  2. Jules says:

    I kind-of like Java’s approach to this issue: exit when all of the threads have exited, except those that have marked themselves as "daemon" threads.  It has its downsides (tracking down application exit bugs when you’ve forgotten to mark one of your threads as a daemon can be annoying) but seems to work reasonably well in most circumstances.

  3. Gabest says:

    Why not just follow the approach of going into standby? If those nasty threads don’t exit within 2 seconds when main one does, just kill them!

  4. Gabest says:

    Ok, maybe not stadby, I meant turning off, when the applications must close. My point was why one is right to do while the other is not.

  5. S says:

    Java’s (and indeed .net’s similar) approach can still bite you if classes you don’t have control over create their own non daemon threads (much like a dll spinning off its own threads). If there’s no cleanup method, you’re hosed.

    You can of course just call System.exit()/Environment.Exit() – but that doesn’t do any special clean up for you, doesn’t run finalizers etc…

  6. jachymko says:

    Adam: it’s not relevant whether CRT() is useful or not (for an example, see http://www.sf.net/projects/console).

    the point is there may be threads which the process isn’t aware of. in your case, how would the injecting process determine it’s time to clean the injected thread up? you’d need some protocol to coordinate the shutdown between the target process and the injecting one. which makes any

  7. jachymko says:

    …injecting quite impossible.

  8. Lackey says:

    ..and to think i believed in William Stallings to the end! :(

  9. Cooney says:

    [And if two unrelated DLLs are both using a common DLL, how do they decide which one should call Common_Cleanup()?]

    Last one out, turn off the lights? Careful with those circular dependencies, though.

  10. cmov says:

    "how do they decide which one should call Common_Cleanup()?"

    The Common_Init function returned a handle. Wether that handle is shared or unique, the data structure behind that handle tells if it’s still initialised or not. Just pass it back to the Cleanup().

    If the library doesn’t work that way, it has a bug IMHO. I always stuff a handle down the applications throat if there’s an init with a corresponding fini. If they don’t eat it, the resulting memory leak/zombied process/whatever isn’t my fault. The handle could be a pointer, an index integer for a pointer array or something else, whatever fits.

  11. [And if two unrelated DLLs are both using a common DLL, how do they decide which one should call Common_Cleanup()? No, wait, don’t answer. ]

    And if two unrelated objects are both using a common object, how do they decide which one should destroy the object?

    (and I think I will call my new invention … "reference counts"!)

  12. cmov says:

    But how does plain reference counting solve the mutual/cross dependency problem?

    (I know, if I have a call to a_fini() in b_fini() and vice versa there’s a problem too. But then, the design of atleast one of the librarys is at fault.)

  13. dave says:

    Well, of course ‘the process’ does know about all of the threads executing it in. ‘The process’ is an exec object, one of whose jobs it is to know about all threads executing in it.

    Which is not just a nit-pick; it leads me to ask whether or not calling the Win32 function ExitProcess does not, in fact, work as advertised: it terminates all threads and then deletes the process.

    Inside Windows 2000 indicates that all attached DLLs are notified during an ExitProcess call. (I don’t have the XP version of the book handy).

    It may not be pretty but it seems though it would get the job done, though I’d maybe hesitate to use that size hammer for normal exits from production code.

  14. cmov says:

    Don’t all win32 programs eventually call ExitProcess before they die naturally? (As opposed to TerminateProcess which is like SIGKILL.)

    [Now I’m sure people don’t read before commenting. The entire first half of the article described how programs exited without calling ExitProcess. -Raymond]
  15. Adam says:

    How about this old-fashioned theory on how processes exit: When a process wants to exit, it releases all the resources it’s allocated, and then exits the main thread (normally by returning from main() or WinMain())

    If a process has a thread pool, it destroys the thread pool. The thread pool is in charge of destroying all threads it created, and not returning to its caller until that’s done.

    If a process has done RPC, it stops RPC (e.g. by calling CoUninitializeEx()). The RPC library is in charge of destroying all the threads *it* created, and not returning to its caller until that’s done.

    If the process has called a DLL, and that DLL has created worker threads, that DLL should have a function to clean up after itself, including all its threads.

    Only if a process has created threads *by hand* does it tell those threads (and *only* those threads) that the show is over, wait for them, and then exit.

    The great thing about this “old-fashioned” way of doing things is that it it doesn’t just work for threads! It works for all kinds of resources. (And with RAII it’s automatic!)

    Still, I guess there’s some reason that this doesn’t work 100% of the time with threads. I’m now looking forward to the next article(s) in the series to learn why…

    [Wouldn’t life be grand if every DLL had a shutdown function? And of course, that shutdown function wouldn’t deadlock or block on the user, and it would magically know to shut things down in the correct order. Sounds like you live in the same ideal world as the people who designed process shutdown. I hope you’re happy together. -Raymond]
  16. Adam says:

    DLLs that allocate resources should have a function to call to release those resources when you don’t want them anymore. Threads are resources. IMHO, any DLL that is not so written is guaranteed to be buggy because of it. (Resource leaks are also bugs.) Counter-examples gladly appreciated though.

    As for ordering, that’s up to the application to define. An app should release resources in the reverse order it acquired them.

    In the previous article that you link to, you point out that A.DLL is buggy in the assumptions it makes, which stem from *not* having an explicit function to release acquired resources and attempting to do it automagically when the DLL is unloaded. Isn’t that article an argument *for* explicit resource release functions?

    [And if two unrelated DLLs are both using a common DLL, how do they decide which one should call Common_Cleanup()? No, wait, don’t answer. The point is that you’re assuming programmers are smart. “Programming is hard. What did you expect? If you’re not up to it, go get a job sorting onions.” -Raymond]

    jachymko > CreateRemoteThread() generally gives me the willies. What on earth is it useful for, anyway? MSDN gives a couple of examples of common use, and then points out that none of those uses are recommended. Still, if you’re going to inject threads into a remote process, you should clean them up. If one process called the equivalent of RemoteMalloc() to allocate memory in another process, or RemoteFOpen() to open a file handle in another process, would you expect the original process to free that memory or close that file? If so, why? (If you’ve got a system in place so that you can transfer ownership of the resource to the remote process, so that it can free those resources, then that’s how you’d do it with the threads too. Transfer ownership of the thread to the remote process, and it would then be responsible for clearing it up.)

  17. And then of course, what do you do about the application that forgets to call X_Cleanup?

    Or better yet, what happens when DLL Foo forgets to call X_Cleanup – your app calls Foo_Cleanup, but Foo forgot to call X_Cleanup.  Now your app never goes away.

    And, of course Foo.Dll was written 10 years ago and the company that makes it no longer supports it – but your code requires it.  Gotta love the giblets.

  18. Dean Harding says:

    It’s great that everybody can come with a perfect design for library termination. I’d *love* for all libraries to come with an X_Cleanup() function that automatically handles multiple initializations, circular references and so on.

    But, getting back to reality for a minute, many libraries DO NOT work like that. *Of course* it’s their fault and *of course* it’s a bug, but while I’m waiting for that bug to be fixed, I’ve got to get on with my own job — which means I need to terminate my process *without* waiting for the buggy library.

  19. Chris Becke says:

    [Now I’m sure people don’t read before commenting. The entire first half of the article described how programs exited without calling ExitProcess. -Raymond]

    However, the guy in question is using Visual C++. And the ms visual c-runtime has for the longest time called ExitProcess on the applications behalf when WinMain returns.

    In order to subvert that process one would have to change the entry point symbol under the linker settings, or provide an alternative WinMainCrtEntryPoint implementation.

    Given the Hello-World nature of the sample code, I’d guess that "this person" is somewhat new to windows development and has accidentally changed the entry point symbol on the Visual C++ linker settings to point directly to WinMain.

  20. KJK::Hyperion says:

    Chris Becke, now we’re blaming Visual C++ for standard ANSI C behavior? (i.e. returning from main is the same as calling exit)

  21. KJK::Hyperion says:

    dave, has reading documentation fallen out of fashion? From the SDK page on ExitProcess:

    "[…]

    Exiting a process causes the following:

    1. All of the object handles opened by the process are closed.
    2. All of the threads in the process, except the calling thread, terminate their execution. The entry-point functions of all loaded dynamic-link libraries (DLLs) are called with DLL_PROCESS_DETACH. After all attached DLLs have executed any process termination code, this function terminates the current process, including the calling thread.

    3. The state of the process object becomes signaled, satisfying any threads that had been waiting for the process to terminate.

    4. The states of all threads of the process become signaled, satisfying any threads that had been waiting for the threads to terminate.

    5. The termination status of the process changes from STILL_ACTIVE to the exit value of the process.

    […]"

    What doesn’t work as advertised?

  22. dave says:

    >What doesn’t work as advertised?

    Nothing, and I did read the documentation, as I thought was apparent (the ‘as advertised’ part).

    My dilemma was that I understood that ‘the fine old fashioned way’ of calling ExitProcess could not possibly fail to work, and furthermore the Win32 documentation seemed to agree with me.  

    So what if there were threads that I didn’t know about in my application code? They were doomed in any case. I didn’t have to know to terminate them, because ExitProcess was going to do that for me.

    Yet the subject of this thread was that the old-fashioned theory was not correct.  So did that mean that the ExitProcess documentation was wrong in some respect?

    I guess I should now head on over to “part 2” of the discussion.

    [The old-fashioned theory is neither correct nor incorrect since was a set of expectations, not a experimental truth. But you yourself noted that those expectations don’t always hold up. Those “doomed” threads can be your downfall. -Raymond]
  23. cmov says:

    "Now I’m sure people don’t read before commenting. The entire first half of the article described how programs exited without calling ExitProcess."

    Ah, sorry. I was a bit puzzled when I read TFA, but now I remember something about using ExitThread to end the program. IIRC it worked for me, but it’s been quite a while since I learned about ExitProcess ;)

    And yes, I have my own entry point. No, it’s not WinMain because that has a whole different prototype.

  24. Norman Diamond says:

    Tell all its threads that the show is over.

    Wait for each of those threads to finish up.

    Even that can be dangerous.  I don’t blame Windows for this, I just observe that it can be dangerous.

    Example:  Subthread gets a notification that a particular resource is going to be unstable (for some known reason), so subthread sleeps for 60 seconds before resuming its monitoring.  User uses the "Services" applet to shut down the service.  Main thread waits for subthread to terminate.  30 seconds pass.  Windows kills both threads.  User starts the service again.  It behaves oddly because some resources leaked and need a reboot.

    I’m not sure if the resource leakage could be solved by having the main thread wait for a timeout, call the dangerous TerminateThread, close known handles and then exit itself.  (I solved the problem differently.)

    Fortunately Windows records a particularly well worded message in the event log when it terminates a non-responding service.  Otherwise I’d probably still be uselessly looking in a different .exe file where the code’s "owner" thought the problem was.

    "Programming is hard. What did you expect? If

    you’re not up to it, go get a job sorting

    onions."  [sarcasm observed]

    Wrong.  It’s go get a job firing anyone who knows how to program.

  25. Chris Becke says:

    @ KJK::Hyperion

    Blaming Visual C++ for standard ANSI C behavior? Not at all. The "some person" however thats having the exit problem did state that they were using Visual C++, and did include a code sample showing a WinMain.

    This makes me confused on two fronts :- On the one, how is this person getting this lockup? He is creating a thread that never terminates, but is falling out of WinMain in an ANSI C compliant environment. ExitProcess() should be called? Why not? He must have specified a custom entry point. Perhaps he did it by accident by adding "WinMain" as the entry point symbol in the MSVC++ linker settings. Yes the signatures are different but the linker doesn’t know that, and given the only function above WinMain would be BaseProcessStart the resulting stack silliness might not be ehough to crash (and thus cause process exit).

    On the other front, what is this "old days" that Raymond Chen alludes to. ANSI C is pretty damn old. It predates my entire programming career, and, as pointed out, its standard behaviour has always been to call exit() after main() returns.

    At least some subset of developers has always been exposed to the idea that simply exiting your "main" thread will terminate the application – regardless of any outstanding threads.

  26. WikiServerGuy says:

    Still, you learned enough today to be able to solve this person’s problem.

    Chris Becke is right.  When I first read this entry it sort me back a bit as I was sure the MSVC CRT WinMain always called ExitProcess, thus terminating all secondary threads…

    Even went and tested it myself:


    #include "stdafx.h"

    #include <process.h>

    void myThread(void *param){

         while(1)

                 ;//nothing

    //if loop commented everything is OK and program exits, otherwise

    //windows is closing but program wouldn’t exit.

    }

    LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wparam,LPARAM

    lparam){

           if (Message == WM_DESTROY ){

                   PostQuitMessage(0);

                   return 0;

           }

        return DefWindowProc(hwnd, Message, wparam, lparam);
    

    }

    void main() {}

    int __stdcall WinMain(HINSTANCE hinstance, HINSTANCE

    hPrevInstance,LPSTR lpszCmdLine, int nCmdShow){

    // variables defined

    HINSTANCE hinst = GetModuleHandle(NULL);
    
    WNDCLASSEX wcex;
    
    wcex.cbSize = sizeof(WNDCLASSEX); 
    
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    
    wcex.lpfnWndProc    = (WNDPROC)WndProc;
    
    wcex.cbClsExtra     = 0;
    
    wcex.cbWndExtra     = 0;
    
    wcex.hInstance      = hinst;
    
    wcex.hIcon          = NULL;
    
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    
    wcex.lpszMenuName   = (LPCSTR)NULL;
    
    wcex.lpszClassName  = &quot;MainWndClass&quot;;
    
    wcex.hIconSm        = NULL;
    
    RegisterClassEx(&amp;wcex);
    
    MSG msg;
    

           HWND hwndMain = CreateWindow("MainWndClass",

    "Sample",WS_OVERLAPPEDWINDOW,100,100,250,210,NULL,NULL, hinst,NULL);

           if(!hwndMain)

                   return FALSE;

           ShowWindow(hwndMain,SW_SHOWNORMAL);

           UpdateWindow(hwndMain);

           _beginthread(myThread, 0,NULL);

        int bRet;
    

           while((bRet=GetMessage(&msg,NULL,0,0))!=0){

                   if (bRet == -1){}

                   else{

                           TranslateMessage(&msg);

                           DispatchMessage(&msg);

                   }

           }

           return (int)msg.wParam;

    }


    Exits fine with standard CRT entry point; but if you set custom it never exits as expected.

    Sort of feel sorry for the person in that linked thread; at least he got a response though… hopefully one of the things that person reads points out that ExitProcess gets called by the CRT on exit :).

  27. Norman Diamond says:

    Tuesday, May 08, 2007 4:07 AM by Chris Becke

    falling out of WinMain in an ANSI C compliant environment.

    Huh?

    Tuesday, May 08, 2007 6:12 AM by WikiServerGuy

    void main() {}

    int __stdcall WinMain(HINSTANCE hinstance,

     HINSTANCE hPrevInstance, LPSTR lpszCmdLine,

     int nCmdShow){

    […]

    }

    Huh?????  OK, I suppose you could detect that the MS VC CRT calls ExitProcess, but I can’t quite figure out what environment your code could be valid in.

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index