Process exit on Windows and Linux



  • @jmp said:

    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:

    That depends heavily on ownership semantics, which are ambiguous from that class definition. Are bar and baz owned components of Foo, or do they point to external objects? That distinction must be encoded in ~Foo in order for the above behavior to occur, just as parent and child processes must have some understanding about what should happen if either the parent or a child terminates unexpectedly.



  • Foo allocates bar and baz itself, just as the child process starts its own children itself. All internal, invisible to the external world unless it starts groveling through data structures it's got very little business touching.



  • So why use pointers at all? Why not embed the child in the parent? You don't need another human to digest your food. You have a stomach for that.



  • @jmp said:

    friggin' everything is already in a job in win7.

    While true it suffices to break away the initially launched process from the parent job, as presumably the processes themselves won't be using jobs. It is actually not difficult and works like a charm.



  • I was looking at windows process management recently. Looks like wmi is the way to go these days; here's the first hit on a search for wmi kill child processes. Probably not immune to race-conditions, but still doable.



  • Wait, I thought the term "child process" was purely cosmetic? I didn't realize some people might think there was an actual relationship between the processes...


  • Banned

    On Linux, there is.



  • That's great when discussing Windows 🚎


  • Banned

    Here. Better?


  • Java Dev

    You can't do this guaranteed clean on linux either (without virtualization). Although the default action on parent death is to send a signal that kills the client, the client can request a different or no signal, or it can handle the signal without exiting (immediately or at all).

    You can explicitly kill a process group, but if you use a signal like SIGTERM that is not always fatal (clean exit is merely a convention) there is no guarantee all processes will die. And any process is free to change its own process group, or start a new one.



  • I'm going to jump on the "and why the fuck would it" bandwagon. Just because Process A spawned Process B does not mean that when Process A shuts down so should Process B (and shut down cleanly, no less).

    Perhaps you are conflating "process" with "thread".



  • Threads don't work that way either, unless you mean that exiting a process destroys all threads in said process.

    On this note I always found it odd that the "main thread" has to be the last thread to exit.



  • That's exactly what I mean, because it sounds like that is what he thinks should happen with processes.


  • FoxDev

    @anotherusername said:

    Perhaps you are conflating "process" with "thread".

    Processes are hard Yo.

    INB4: giggity


  • Discourse touched me in a no-no place

    @LB_ said:

    I didn't realize some people might think there was an actual relationship between the processes...

    @Gaska said:

    On Linux, there is.

    And Windows.

    root      1272  0.0  0.0 287464  2880 ?        Sl   Jan05   0:28 /usr/lib/accountsservice/accounts-daemon
    root      1429  0.0  0.1 613848  5024 ?        Ssl  Jan05   4:07 /usr/bin/docker -d
    root      1743  0.0  0.1 226104  5820 ?        Sl   Jan05   0:17  \_ docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 2222 -container-ip 172.17.0.1 -container-port 22
    root      1752  0.0  0.0 226104  2004 ?        Sl   Jan05   0:17  \_ docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 444 -container-ip 172.17.0.1 -container-port 443
    root      1761  0.0  0.0 160568  1940 ?        Sl   Jan05   0:17  \_ docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 81 -container-ip 172.17.0.1 -container-port 80
    root      1767  0.0  0.0  21092  1708 pts/1    Ss+  Jan05   0:00  \_ /bin/bash /sbin/boot
    root      1871  0.0  0.0    196    32 pts/1    S+   Jan05   0:21      \_ /usr/bin/runsvdir -P /etc/service
    root      1872  0.0  0.0    176     0 ?        Ss   Jan05   0:00          \_ runsv nginx
    root      1881  0.0  0.0 125612  2476 ?        S    Jan05   0:00          |   \_ nginx: master process /usr/sbin/nginx
    www-data  1912  0.0  0.0 126452  3244 ?        S    Jan05   1:35          |       \_ nginx: worker process
    www-data  1913  0.0  0.0 126460  3088 ?        S    Jan05   0:55          |       \_ nginx: worker process
    www-data  1914  0.0  0.0 125816  1448 ?        S    Jan05   0:08          |       \_ nginx: cache manager process
    root      1873  0.0  0.0    176     0 ?        Ss   Jan05   0:00          \_ runsv sshd
    root      1878  0.0  0.0  61376  2544 ?        S    Jan05   0:00          |   \_ /usr/sbin/sshd -D -e
    root      1874  0.0  0.0    176     0 ?        Ss   Jan05   0:00          \_ runsv redis
    landsca+  1880  0.6  0.0  42452  3584 ?        Sl   Jan05  63:30          |   \_ /usr/bin/redis-server *:6379               
    root      1875  0.0  0.0    176     0 ?        Ss   Jan05   0:00          \_ runsv unicorn
    pjh       1877  0.1  0.0  30708  3620 ?        S    Jan05  15:19          |   \_ /bin/bash config/unicorn_launcher -E production -c config/unicorn.conf.rb
    pjh       1982  0.3  3.2 402184 129084 ?       Sl   Jan05  30:40          |       \_ unicorn master -E production -c config/unicorn.conf.rb                                              
    pjh       3070  1.0  5.1 1223148 201952 ?      Sl   Jan05 102:04          |       |   \_ sidekiq 3.5.0 discourse [0 of 5 busy]                                                               
    pjh       3078  0.3  3.9 994688 154580 ?       Sl   Jan05  32:14          |       |   \_ unicorn worker[0] -E production -c config/unicorn.conf.rb                                           
    pjh       3101  0.3  4.5 1042816 177520 ?      Sl   Jan05  33:16          |       |   \_ unicorn worker[1] -E production -c config/unicorn.conf.rb                                           
    pjh       3114  0.3  4.7 1040744 184908 ?      Sl   Jan05  34:19          |       |   \_ unicorn worker[2] -E production -c config/unicorn.conf.rb                                           
    pjh      13003  0.0  0.0  19976  3444 ?        S    15:53   0:00          |       \_ sleep 1
    root      1876  0.0  0.0    176     0 ?        Ss   Jan05   0:00          \_ runsv rsyslog
    syslog    1879  0.0  0.0 180152  2828 ?        Sl   Jan05   0:04          |   \_ rsyslogd -n
    root      1882  0.0  0.0    176     0 ?        Ss   Jan05   0:00          \_ runsv cron
    root      1885  0.0  0.0  26780  1756 ?        S    Jan05   0:02          |   \_ cron -f
    root      1884  0.0  0.0    176     0 ?        Ss   Jan05   0:00          \_ runsv postgres
    message+  1886  0.0  0.1 386252  6108 ?        S    Jan05   0:59              \_ /usr/lib/postgresql/9.3/bin/postmaster -D /etc/postgresql/9.3/main
    message+  1977  0.0  0.1 386384  6216 ?        Ss   Jan05   0:06                  \_ postgres: checkpointer process                                    
    message+  1978  0.0  0.0 386384  2252 ?        Ss   Jan05   0:13                  \_ postgres: writer process                                          
    message+  1979  0.0  0.0 386384  2312 ?        Ss   Jan05   0:29                  \_ postgres: wal writer process                                      
    message+  1980  0.0  0.1 387124  4004 ?        Ss   Jan05   0:47                  \_ postgres: autovacuum launcher process                             
    message+  1981  0.0  0.0 104100  2496 ?        Ss   Jan05   2:05                  \_ postgres: stats collector process                                 
    kernoops  1486  0.0  0.0  37148  1368 ?        Ss   Jan05   0:24 /usr/sbin/kerneloops
    root      1681  0.0  0.0 253044  3696 ?        Ss   Jan05   1:03 /usr/sbin/apache2 -k start
    www-data 24844  0.0  0.0 253556  3232 ?        S    Jan10   0:00  \_ /usr/sbin/apache2 -k start
    www-data 24845  0.0  0.0 253556  3136 ?        S    Jan10   0:00  \_ /usr/sbin/apache2 -k start
    www-data 24846  0.0  0.0 253556  3192 ?        S    Jan10   0:00  \_ /usr/sbin/apache2 -k start
    www-data 24847  0.0  0.0 253556  3144 ?        S    Jan10   0:00  \_ /usr/sbin/apache2 -k start
    www-data 24848  0.0  0.0 253556  3044 ?        S    Jan10   0:00  \_ /usr/sbin/apache2 -k start
    www-data 28744  0.0  0.0 253556  3628 ?        S    Jan10   0:00  \_ /usr/sbin/apache2 -k start
    www-data  6436  0.0  0.0 253556  3528 ?        S    Jan10   0:00  \_ /usr/sbin/apache2 -k start
    www-data 18163  0.0  0.0 253556  3652 ?        S    Jan11   0:00  \_ /usr/sbin/apache2 -k start
    www-data 20743  0.0  0.0 253548  3632 ?        S    11:25   0:00  \_ /usr/sbin/apache2 -k start
    www-data 20745  0.0  0.0 253548  3672 ?        S    11:25   0:00  \_ /usr/sbin/apache2 -k start
    root      1728  0.0  0.0  15824  1456 tty1     Ss+  Jan05   0:00 /sbin/getty -8 38400 tty1
    


  • @LB_ said:

    Wait, I thought the term "child process" was purely cosmetic? I didn't realize some people might think there was an actual relationship between the processes...

    There is.

    Not sure about on Windows, but on UNIX the processes are considered related until you use the setsid system call on the child to move a process to a new process group. Usually this is done immediately after you fork the new process.



  • @ben_lubar said:

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

    SIGKILL isn't so much about sending a signal to the process as it is sending a note to init to take the process out back and shoot it.



  • @PJH said:

    @LB_ said:
    I didn't realize some people might think there was an actual relationship between the processes...

    @Gaska said:

    On Linux, there is.

    And Windows.

    Yes, but what does it actually mean, apart from when you want to go into Task Manager and "End Process Tree"? It remembers the relationship, but I don't think it automatically gives the two processes any kind of special parent-child bond.


  • FoxDev

    The only thing that comes to mind is the child process inherits the parent's security descriptor


  • FoxDev

    @RaceProUK said:

    The only thing that comes to mind is the child process inherits the parent's security descriptor

    unless of course the parent explicitly asks for the child process to get a different one of course.

    although that is a bit of an odd case...



  • @RaceProUK said:

    The only thing that comes to mind is the child process inherits the parent's security descriptor

    In the default case, yes, although there's nothing unique about it. And after the initial process creation is completed, it doesn't really matter anymore.

    @accalia said:

    unless of course the parent explicitly asks for the child process to get a different one of course.

    although that is a bit of an odd case...

    Isn't that how elevation normally takes place?


  • FoxDev

    IIUC, an elevated process doesn't lose its original security descriptor; it just operates under a different one on a temporary basis.


  • FoxDev

    @anotherusername said:

    Isn't that how elevation normally takes place?

    yes, ish?

    elevation is the process assuming a more permissive security descriptor rather than spawning a child one with a more permissive descriptor.

    because the child can ask for a different security descriptor too, it might not get it (security), but it can ask for it



  • Ah... maybe more like "Run as...", then?



  • @PJH said:

    And Windows.

    IIRC that is only the process creation metadata and isn't really significant. Or that's what I've been told.

    @powerlord said:

    There is.

    Are they just siblings or actually child and parent? I was under the impression they were siblings, but I don't work with *nix very often.

    @RaceProUK said:

    The only thing that comes to mind is the child process inherits the parent's security descriptor

    That's not really a long term relationship though, changing that for the parent doesn't change it for existing children (I assume).


  • FoxDev

    @LB_ said:

    @RaceProUK said:
    The only thing that comes to mind is the child process inherits the parent's security descriptor

    That's not really a long term relationship though, changing that for the parent doesn't change it for existing children (I assume).

    Correct; I guess I should have said

    the child process inherits a copy of the parent's security descriptor

    which is more technically correct.



  • FWIW, when I start Task Manager (using Ctrl-Shift-Esc; if you use the Run window, it starts under explorer.exe but other than that it's the same)

    Then click "Show processes from all users", which launches an elevated instance of the process, and if you're quick you'll see this happen:

    Then the non-elevated process (PID 6044 in those screenshots) exits, orphaning the new process (PID 8972):

    Edit: the screenshots were taken from a separate instance of Process Explorer.


  • :belt_onion:

    Yeah, Windows processes can't change their uid IIRC. That's why taskmgr (and other programs that need to do the same thing) do that



  • @LB_ said:

    Are they just siblings or actually child and parent? I was under the impression they were siblings, but I don't work with *nix very often.

    henke37 mentioned this earlier:

    https://what.thedailywtf.com/t/process-exit-on-windows-and-linux/53959/37?u=powerlord

    Specifically, if you don't break the relationship using setsid (or setpgrp, but you should never use that one), the parent process gets sent a SIGCHLD signal when the child exits.



  • @jmp said:

    From the MSDN page on ExitProcess :

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

    Who designed this shitty operating system?!

    Sounds like a good design to me.

    Would you really want a system where process termination forces termination of all its children?

    Would you want all your apps (and maybe even your login session) to terminate because the Explorer process crashes? If you launch a GUI-based app from a console window, would you want it to be forcibly terminated when you quit the terminal app?

    There are plenty of reasons why you may want to launch a child process that is capable of outliving its parent.

    @jmp said:

    ... Distributed batch processing ...

    Stuff like this, when it starts failing, is nasty evil and ugly under all circumstances. Figuring out how to kill a huge set of distributed child processes is only the tip of the iceberg here, I'm afraid.



  • @David_C said:

    Would you want all your apps (and maybe even your login session) to terminate because the Explorer process crashes? If you launch a GUI-based app from a console window, would you want it to be forcibly terminated when you quit the terminal app?

    ... I feel like you're only reading the Windows half of this conversation.

    If you did this, you would have a system call to sever the connection between the child and parent process for programs that are not intended to be child processes belonging to a parent.

    On UNIX, this is void setsid(), which I mentioned twice in this thread already.



  • @henke37 said:

    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.

    To be fair, the default action for SIGCHLD is to ignore the signal, so you have to ask for your control flow to be hijacked by changing the signal's handler.

    Then again, I wish for somewhat more unified event handling in various POSIX and *nix APIs. Waiting on multiple different objects (e.g., file/socket plus a condition variable for example) is a mess. OTOH MsgWaitForMultipleObjects seems nice but has (had?) a ridiculously low maximum number on the number of objects it can wait on (like 64 or something silly).



  • @powerlord said:

    ... I feel like you're only reading the Windows half of this conversation.

    ... I feel like you ignored the fact that my post was in reply to two specific messages and not the entire thread, and that you believe I wrote something that disagrees with what you wrote.



  • @jmp said:

    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.

    How very north korea of you.


  • Java Dev

    Well, you can wait for file descriptors and a signal at the same time with pselect or ppoll. And you can get certain things redirected to file descriptors. But indeed AFAIK you can't easily add a condition to the mix. Easiest solution is probably to use a pipe instead...



  • @jmp said:

    we have no idea if they've spawned children themselves.

    (someone may have mentioned this already, I haven't caught up yet)

    SysInternals Process Explorer shows a tree of processes, so it would appear that it is possible to know the children a process has spawned.



  • @PleegWat said:

    Well, you can wait for file descriptors and a signal at the same time with pselect or ppoll. And you can get certain things redirected to file descriptors. But indeed AFAIK you can't easily add a condition to the mix. Easiest solution is probably to use a pipe instead...

    Yea, the pipe is what I do now. IIRC using signals seemed surprisingly tricky w.r.t. race conditions - although I can't remember now what the problem was exactly.

    Futexes seemed like a solution at some point, with FUTEX_FD, but that was removed again because it was "inherently racy".

    I used to wish for a unified way for waiting on various events, but .. the whole thing becomes pointless as soon as you start adding third party libraries. Few of them expose the underlying primitives anyway, so ... meh.



  • @dcon said:

    SysInternals Process Explorer shows a tree of processes, so it would appear that it is possible to know the children a process has spawned.

    Yes, it does this by reading the process creation metadata, which I have been told is not used for anything other than being meta. Unfortunately I am having a hard time finding the source of this info, I don't remember what I was searching for at the time that led me to this possibly not true statement.



  • @dcon said:

    SysInternals Process Explorer shows a tree of processes, so it would appear that it is possible to know the children a process has spawned.

    Race conditions.


  • Java Dev

    The fundamental unit is usually the file descriptor. I never closely looked into futexes, but they are inherently hard to use because they assume a certain part of the work is done in userspace via atomic instructions.

    Signals require using pselect or ppoll and supplying a signal mask. For example, if you rely on SIGHUP to trigger a configuration load, you'll have it blocked normally but unblocked using the signal mask option to pselect and ppoll. This ensures arrival of that signal always interrupts the call.

    Relying on signals while using normal select or poll (or sleep/nanosleep, if no file descriptors are involved) is racy because the signal might arrive between unblocking the signal (or testing the condition) and going into the select (or whatever) call.



  • @PleegWat said:

    Signals require using pselect or ppoll and supplying a signal mask. For example, if you rely on SIGHUP to trigger a configuration load, you'll have it blocked normally but unblocked using the signal mask option to pselect and ppoll. This ensures arrival of that signal always interrupts the call.

    Yeah, as said, I don't really remember what the problem was. It seems like ppoll plus one of the SIGRT* signals (inter-thread) should have done the trick.



  • Ended up reading a bit more about this, and seems like there's

    • eventfd as an alternative to the pipe, if one uses it for signalling only
    • signalfd which allows one to receive signals via a fd.


  • @LB_ said:

    @dcon said:
    SysInternals Process Explorer shows a tree of processes, so it would appear that it is possible to know the children a process has spawned.

    Yes, it does this by reading the process creation metadata, which I have been told is not used for anything other than being meta. Unfortunately I am having a hard time finding the source of this info, I don't remember what I was searching for at the time that led me to this possibly not true statement.

    Yeah, and the main problem with this is that in Windows, processes can be orphaned, because unlike unixoid systems, the existence of a child process doesn't prevent the termination of a process, not even the destruction of a process object.

    This is, of course, a blessing, because otherwise it would be impossible for a program to explicitly wait for its parent to terminate (as is required for, say, an automatic update mechanic).





  • @jmp said:

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

    
    

    class Foo {
    // stuff
    private:
    std::unique_ptr<Bar> bar;
    std::unique_ptr<Baz> baz;
    };

    
    HTH, HAND, ZOMG, BBQ, ETC


  • @tar said:

    ```
    class Foo {
    // stuff
    private:
    std::unique_ptr<Bar> bar;
    std::unique_ptr<Baz> baz;
    };

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

    No need to use any pointers of any kind.



  • ...unless Bar or Baz is abstract.



  • ...Or is the output of a Factory, some kind of ProcessFactory maybe...



  • @jmp said:

    ...Or is the output of a Factory, some kind of ProcessFactory maybe...

    I thought dependency injection was all the rage these days? My point still holds if bar and baz are move-constructed or copy-constructed from constructor parameters. (However, if polymorphism is involved, then yeah, a smart pointer is needed.)



  • Could also be intended to use the Pimpl idiom (hide implementation details behind a pointer so including the class header doesn't include implementation specific headers).


Log in to reply