Process exit on Windows and Linux



  • From the MSDN page on ExitProcess :

    Exiting a process does not cause child processes to be terminated.

    Who designed this shitty operating system?!

    For reference to get the sane behavior on termination of a child process that might have made further children you have to create a 'job object', create processes suspended, add them to the job object, resume the processes, and then kill the job object later. Which kills processes as if TerminateProcess were called instead of a clean exit, and if the child process wants to make its own job objects you're fucked because they /can't be nested/ up until windows 8, and even then there are some weird requirements on what you can do with nested job objects.

    Suspicion: C and C++ are much more popular in the unix world because the POSIX/Linux APIs aren't nightmare hellstews like the windows API is.


  • Notification Spam Recipient

    Huh. TMYK.


  • FoxDev

    TR :wtf: is you think a function called ExitProcess (singular!) should exit processes plural.



  • Hmm this might be misdirected. From reading around, apparently parent process exit doesn't guarantee child exit on UNIX either.

    All operating systems suck.

    (There are slightly better solutions in UNIX-land, though. "PR_SET_PDEATHSIG" is the relevant option, lets you say "When my parent dies send me this signal". Also a better equivalent of job objects and some other cute portable solutions).


  • Notification Spam Recipient

    @jmp said:

    "When my parent dies send me this signalgreeting card"

    😢



  • @RaceProUK said:

    TR :wtf: is you think a function called ExitProcess (singular!) should exit processes plural.

    "I don't understand why I'm getting this memory leak; I'm definitely deleting everything"
    "Ah, here's your problem. You're only deleting the root node of the tree, you have to traverse the entire thing and delete every node yourself".


  • FoxDev

    Right, so you've completely ignored the fact it's ExitProcess SINGULAR. As in it exits ONE process. Not two, not five, not twenty. ONE.



  • And "delete a;" doesn't say "delete a and also everything that has a child relationship with a;", but if a's destructor doesn't delete its children it's probably a mistake.


  • FoxDev

    And that's different from ExitProcess how?

    Face it, all you're doing here is "This function does what it's designed to do, not what I want it to! WAH!".



  • @RaceProUK said:

    And that's different from ExitProcess how?

    Err... it isn't? I assume that's intended to be rhetorical and/or socratic, but I don't see the difference. When you destroy things, their children should go with them.

    @RaceProUK said:

    Face it, all you're doing here is "This function does what it's designed to do, not what I want it to! WAH!".

    How dare I be upset that because of API design decisions that look like a mistake to me there's no reliable way to kill an entire process tree in Windows!


  • FoxDev

    @jmp said:

    When you destroy things, their children should go with them.

    It's clear you don't get how this works.

    When you delete a;, a's destructor cleans up a's children.
    When you ExitProcess, the process's exit handler cleans up its children.

    So yes, you're throwing a hissy fit because the API is behaving exactly the way it's documented, instead of somehow being clairvoyant enough to guess what you want.


  • Discourse touched me in a no-no place

    @RaceProUK said:

    TR :wtf: is you think a function called ExitProcess (singular!) should exit processes plural.

    If you want to kill child process when the parent dies, there's other ways to do it--send a message, trip a shared semaphore, etc.



  • You seem to be taking this somewhat personally...

    I'm modeling the "process' exit handler" here as being the stuff that runs after process termination and frees memory it allocated but never released, releases handles it never got around to releasing, etc.. I would have expected that catch-all handler to do all the relevant destructory things, like cleaning up children. Obviously it isn't, possibly because the code killing it (which I didn't write, to forestall that particular criticism) is calling TerminateProcess and that kills it hard. AFAICT the only way to actually get this code I don't control to exit cleanly is to start a remote thread in it and call ExitProcess from there, which seems mighty hacky.

    Obviously the API is behaving the way it's documented, I just think the way it's documented to behave is not how it should behave.

    I should be clearer about problem space, maybe. Program A is under my control, it starts arbitrary other programs. One such program is cmd running a batch file that then starts program C, not under my control. Program A occasionally wants to kill its children, and when it does so I don't necessarily know whether or how many children it's got, and the entire tree has to go. And doing that in Windows is a) a giant pain and b) unreliable, because the job-object solution doesn't work if children want to make job objects, and the walk-the-process-tree solution has race conditions.


  • FoxDev

    Exactly. After all, who better to clean up a process's children than the process that spawned them?


  • FoxDev

    @jmp said:

    I'm modeling the "process' exit handler" here as being the stuff that runs after process termination

    And therein lies your error; the exit handler runs before process termination.

    @jmp said:

    Obviously the API is behaving the way it's documented, I just think the way it's documented to behave is not how it should behave.

    So use an API that does behave the way you want.

    @jmp said:

    Program A occasionally wants to kill its children, and when it does so I don't necessarily know whether or how many children it's got

    Then Process A is designed badly, and needs a rethink.



  • @jmp said:

    You seem to be taking this somewhat personally...

    Here we go again...?



  • @RaceProUK said:

    And therein lies your error; the exit handler runs before process termination.

    Uh, if you call TerminateProcess() on something it still releases all memory it has allocated.

    @RaceProUK said:

    So use an API that does behave the way you want.

    There isn't one.

    @RaceProUK said:

    Then Process A is designed badly, and needs a rethink.

    That wasn't clear, sorry; process A doesn't know if any of the children it has started have child processes themselves. That is, if A starts B, A doesn't know if B has child C. A is closer to a shell here than a system that can fully specify all behaviours of children.

    Programs that can start arbitrary children can't possibly know if those children themselves have children.


  • FoxDev

    @tar said:

    Here we go again

    On my own,
    Going down the only road I'll ever know.
    Like a drifter, I was born to walk alone.
    And I've made up my mind, I ain't wasting no more time…

    😄


  • FoxDev

    @jmp said:

    Uh, if you call TerminateProcess() on something it still releases all memory it has allocated.

    Wait, I thought you were calling ExitProcess? If you're calling TerminateProcess, no wonder you're hitting issues; TerminateProcess kills the process immediately, bypassing the exit handler entirely, and with absolutely no consideration for unflushed buffers and other stuff.



  • Yes, sorry, I haven't been very clear here. I was quoting the ExitProcess MSDN entry because I was surprised that normal-process-exit doesn't result in children dying, and it describes normal process exit.

    I didn't actually write the code, keep in mind, I'm just coming in later after the guy who originally wrote it quit and am trying to get it to work.

    Looking around on the wob the only solution I can find for cleanly exiting arbitrary child processes is this one, which is basically "Call CreateRemoteThread() to make that process call ExitProcess". Ugly.


  • :belt_onion:

    So, I know I'm pulling a Blakeyrat here, but why do you need to kill a process and its children?


  • Notification Spam Recipient

    @sloosecannon said:

    t why do you need to kill a process and its children?

    To control an uncontrollable situation, probably?


  • FoxDev

    @jmp said:

    I didn't actually write the code, keep in mind, I'm just coming in later after the guy who originally wrote it quit and am trying to get it to work.

    Ah. In that case, I apologise for my earlier attitude; maintaining other people's code is not an enviable task ;)

    @jmp said:

    Looking around on the wob

    You might have better luck on the Web 😛



  • Distributed batch processing. Nodes get programs handed out to them to execute, execute them. Sometimes there's an error state or everything finishes and the children need to go away. Some of the children are third-party products we don't have direct control over, so we can't specify an interface to get them to cleanly shut down, and we have no idea if they've spawned children themselves.

    This is actually happening and causing a problem, because one of the third-party products wants to be started via a batch script that sets up a bunch of environmental variables, so the direct child started by the node is cmd.exe and then cmd.exe has a child that is the complicated shitty third-party thing.

    Setting up the environment variables beforehand and starting the third-party thing directly would be nontrivial and wouldn't fix the underlying problem.

    EDIT: And the things we're executing are part of a group that has to run together, not individual components doing their own thing, so sometimes the child thing needs to be quit after some other node's child fell over.


  • :belt_onion:

    @jmp said:

    normal-process-exit doesn't result in children dying

    I think this is precisely for things like Explorer, which sometimes crashes/restarts and you would not want all its children processes to die...


  • :belt_onion:

    Jesus.
    *fancy hand waves*
    May God have mercy on your soul.


  • FoxDev

    It's starting to sound like what you really need is a sandbox, or possibly a VM you can terminate and restart in a known state.



  • @RaceProUK said:

    Ah. In that case, I apologise for my earlier attitude; maintaining other people's code is not an enviable task 😉

    No worries. I was a bit surprised; was trying to work out if I'd previously given you some reason to dislike me. 😛

    @sloosecannon said:

    Jesus. fancy hand waves May God have mercy on your soul.

    It's a distributed system with ridiculous amounts of shared state, what could possibly go wrong?

    @RaceProUK said:

    It's starting to sound like what you really need is a sandbox, or possibly a VM you can terminate and restart in a known state.

    That is an excellent idea.


  • Discourse touched me in a no-no place

    @jmp said:

    Programs that can start arbitrary children can't possibly know if those children themselves have children.

    Yeah, and I already told you two ways to deal with that. There's other variations. The "classic" unix-style one being "parent creates a lock file on startup and deletes it on exit; children all watch for the existence of the file and quit when it disappears". You can do that with shared memory or something, too.


  • :belt_onion:

    @jmp said:

    It's a distributed system with ridiculous amounts of shared state, what could possibly go wrong?

    So basically, you need

    @Tsaukpaetra said:

    To control an uncontrollable situation


  • :belt_onion:

    To be fair, it sounds like he's dealing with third party stuff that he can't change. So you can't do that...


  • Discourse touched me in a no-no place

    BTW, dealing with job objects isn't that much work.

    Look, someone's already shown how to do it:


  • Discourse touched me in a no-no place

    @sloosecannon said:

    To be fair, it sounds like he's dealing with third party stuff that he can't change. So you can't do that...

    Yeah, now that he's described the problem, that's true.

    Fortunately job objects don't look like that hard to work with.



  • Read the comments; job objects aren't fit for purpose below windows 8 because they don't recurse and friggin' everything is already in a job in win7. Also there's a window for half-created zombie processes (parent dies after CreateProcess but before adding the process to the job) but that's probably not too big a problem here.

    EDIT: One big example: debuggers tend to put things into a job object.


  • Discourse touched me in a no-no place

    *cough* well, I guess you could buy one of those macro recorder programs and drive Task Manager to select the parent process and send "Kill Process Tree" or something.

    Also, you could get with the program and stop using an OS from 2009.



  • @sloosecannon said:

    why do you need to kill a process and its children?

    Because it's a very naughty process?



  • I'd like to point the blame at the POSIX design instead. The entire concept of a parent being interrupted in the middle of work to be notified that it has to go and collect the exit code of the child process is moronic.

    If I want to be notified, I call a function such as MsgWaitForMultipleObjects in my main loop. Then I'm prepared to deal with the situation. None of this nonsense with surprise control flow hijacking.

    There is also the entire idiotic design decision to reparent processes to process # 1. That's just wrong. Don't even bother sending the signal if there isn't anyone there to listen.

    The windows process management api is just better. Any process can, not must, but can, listen for notifications of any other process exiting.



  • If I open a Microsoft Word document that I downloaded from an email by double clicking on the icon in the downloads list, should closing my browser also close Microsoft Word?



  • Unless you're writing libc, that distinction is meaningless.



  • Wrong post to reply to I think.

    Does ShellExecute actually launch the process as a child of the process calling ShellExecute? I thought it handed things off to the shell...



  • delete doesn't delete the targets of pointers, only the fields embedded in the object.

    Are you saying that child processes are embedded in the parent process? Because you might be thinking of threads.



  • If you have

    class Foo {
    // stuff
    private:
        Bar* bar;
        Baz* baz;
    }
    

    I would expect ~Foo to delete bar and baz. I consider that roughly analogous to launching a child process to do something for you and then getting it to exit via some mechanism when it's done. Or stronger, if you had this:

    struct Tree {
        SomeType data;
        Tree* lhs;
        Tree* rhs;
    }
    

    Then I would expect ~Tree to delete lhs and rhs, even though they're public and this is probably some weird intrusive tree thing being manually shuffled around.

    At the very least I'd expect there to be API support for having children exit cleanly when their parent exits cleanly. It's not something I'd expect to have to do entirely manually, because it strikes me as being race-condition prone - like trying to implement join() manually - and it's obviously useful.

    Not really the facilities for it in Windows though I guess. Things have to manually start up and pump the event loop, so there's nowhere to send the signal that's reliable.

    This is mostly my experience with win32: 95% of the puzzle pieces you need are there, but they all have weird knobbly bits on the outside you have to sand down yourself, and the lights are off by default.

    EDIT: Okay so I can get ~Tree to display as inline code: ~Tree. And down here I can get ~Foo to display as inline code: ~Foo. But under the code sample? Not on your life. Discourse!

    EDITEDIT: Something to do with saying 'C++' as the language for the code block? I don't know. Don't know what the actual terms that get the right highlighting are either. Currently it's 'cpp'.



  • @jmp said:

    At the very least I'd expect there to be API support for having children exit cleanly when their parent exits cleanly.

    Okay, now think about what you just said. You want children to die of natural causes at the exact moment that their parents do. The children can keep living after the parent dies. In fact, that's usually what happens. There are two cases:

    • Child process spawned and waited for by parent process
    • Child process spawned and not waited for by parent process

    There is no "cleanly exit another process". You can kill another process, but cleanly exiting is something you can only do to yourself.

    Here's an example: https://play.golang.org/p/S8aJJtrPyd

    go means "start this function concurrently". defer means "run this function when it's time to unwind the stack". When main returns, the program exits. Nothing can happen after main returns. The goroutine that it started doesn't get a chance to clean up because there's no way to cleanly exit another process.

    You can, however, wait for another process to cleanly exit: https://play.golang.org/p/IyAS22miR2

    You can also cooperate with another process to cleanly exit: https://play.golang.org/p/lYHvMFF-Z6



  • Not at the exact moment. Eventually is fine. Consider that Linux has a syscall that sets up your process to receive a particular signal when your parent process dies, and that signal can be SIGHUP - i.e., a request for termination.





  • SIGTERM is also an option, of course.



  • That's still not a clean exit by default.

    Well, at least you're not sending SIGKILL to it...



  • There's convention here, though. It's like sending WM_CLOSE if everybody in windows-land had a message pump by default and were strongly encouraged to handle it, and the default behavior if you didn't think about the issue was at least vaguely sane.

    Sane people who need clean termination will handle SIGTERM by cleanly terminating. Sane people in cases where it doesn't matter will just let the default happen. Insane people will either catch and swallow (unfortunate) or not even think about the issue and get the default, which is probably better than nothing even if it doesn't flush some buffers.



  • Why not just handle SIGINT? That's guaranteed to exist on Windows, Linux, BSD, and Plan9.



  • No facility to send it automatically if parent process dies under windows.

    (Also sending the closest equivalent to SIGINT to a particular process in windows isn't exactly clean because it's associated with a 'console' which is different from a process).


Log in to reply