How to make child process die after parent exits?

194,212

Solution 1

Child can ask kernel to deliver SIGHUP (or other signal) when parent dies by specifying option PR_SET_PDEATHSIG in prctl() syscall like this:

prctl(PR_SET_PDEATHSIG, SIGHUP);

See man 2 prctl for details.

Edit: This is Linux-only

Solution 2

I'm trying to solve the same problem, and since my program must run on OS X, the Linux-only solution didn't work for me.

I came to the same conclusion as the other people on this page -- there isn't a POSIX-compatible way of notifying a child when a parent dies. So I kludged up the next-best thing -- having the child poll.

When a parent process dies (for any reason) the child's parent process becomes process 1. If the child simply polls periodically, it can check if its parent is 1. If it is, the child should exit.

This isn't great, but it works, and it's easier than the TCP socket/lockfile polling solutions suggested elsewhere on this page.

Solution 3

I have achieved this in the past by running the "original" code in the "child" and the "spawned" code in the "parent" (that is: you reverse the usual sense of the test after fork()). Then trap SIGCHLD in the "spawned" code...

May not be possible in your case, but cute when it works.

Solution 4

Under Linux, you can install a parent death signal in the child, e.g.:

#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h>  // perror()

// ...

pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() != ppid_before_fork)
        exit(1);
    // continue child execution ...

Note that storing the parent process id before the fork and testing it in the child after prctl() eliminates a race condition between prctl() and the exit of the process that called the child.

Also note that the parent death signal of the child is cleared in newly created children of its own. It is not affected by an execve().

That test can be simplified if we are certain that the system process who is in charge of adopting all orphans has PID 1:

pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() == 1)
        exit(1);
    // continue child execution ...

Relying on that system process being init and having PID 1 isn't portable, though. POSIX.1-2008 specifies:

The parent process ID of all of the existing child processes and zombie processes of the calling process shall be set to the process ID of an implementation-defined system process. That is, these processes shall be inherited by a special system process.

Traditionally, the system process adopting all orphans is PID 1, i.e. init - which is the ancestor of all processes.

On modern systems like Linux or FreeBSD another process might have that role. For example, on Linux, a process can call prctl(PR_SET_CHILD_SUBREAPER, 1) to establish itself as system process that inherits all orphans of any of its descendants (cf. an example on Fedora 25).

Solution 5

If you're unable to modify the child process, you can try something like the following:

int pipes[2];
pipe(pipes)
if (fork() == 0) {
    close(pipes[1]); /* Close the writer end in the child*/
    dup2(pipes[0], STDIN_FILENO); /* Use reader end as stdin (fixed per  maxschlepzig */
    exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}

close(pipes[0]); /* Close the reader end in the parent */

This runs the child from within a shell process with job control enabled. The child process is spawned in the background. The shell waits for a newline (or an EOF) then kills the child.

When the parent dies--no matter what the reason--it will close its end of the pipe. The child shell will get an EOF from the read and proceed to kill the backgrounded child process.

Share:
194,212

Related videos on Youtube

Paweł Hajdan
Author by

Paweł Hajdan

Updated on June 22, 2021

Comments

  • Paweł Hajdan
    Paweł Hajdan almost 3 years

    Suppose I have a process which spawns exactly one child process. Now when the parent process exits for whatever reason (normally or abnormally, by kill, ^C, assert failure or anything else) I want the child process to die. How to do that correctly?


    Some similar question on stackoverflow:


    Some similar question on stackoverflow for Windows:

  • qrdl
    qrdl over 15 years
    Yeah, Linux-specific. I don't think there is POSIX way to do it.
  • Shrey Shivam
    Shrey Shivam over 14 years
    I found this didn't happen reliably, at least on OS X.
  • Shrey Shivam
    Shrey Shivam over 14 years
    Hmm. The documentation is vague and contradictory on this, but it appears that the parent process must be the lead process for the session, not just the process group. The lead process for the session was always login, and getting my process to take over as lead process for a new session was beyond my abilities at the moment.
  • Takagi
    Takagi about 14 years
    Excellent solution. Continuesly invoking getppid() until it returns 1 and then exit. This is good and I now use it too. A non-pollig solution would be nice though. Thank you Schof.
  • Rob K
    Rob K almost 14 years
    SIGHUP effectively only gets sent to child processes if the exiting process is a login shell. opengroup.org/onlinepubs/009695399/functions/exit.html "Termination of a process does not directly terminate its children. The sending of a SIGHUP signal as described below indirectly terminates children /in some circumstances/."
  • Jonathan Leffler
    Jonathan Leffler almost 14 years
    @Rob: correct - that's what the quote I gave says, too: that only in some circumstances does the child process get a SIGHUP. And it is strictly an over-simplification to say that it is only a login shell that sends SIGHUP, though that is the most common case. If a process with multiple children sets itself up as the controlling process for itself and its children, then the SIGHUP will (conveniently) be sent to its children when the master dies. OTOH, processes seldom go to that much trouble - so I more more nit-picking than raising a really significant quibble.
  • Rob K
    Rob K over 13 years
    I fooled around with it for a couple of hours and couldn't get it to work. It would have nicely handled a case where I have a daemon with some children that all need to die when the parent exits.
  • Jarosław Bielawski
    Jarosław Bielawski over 13 years
    Just for info, on Solaris if you're in a zone, the gettpid() does not become 1 but gets the pid of the zone scheduler (process zsched).
  • Rui Marques
    Rui Marques over 11 years
    If anyone is wondering, in Android systems pid seems to be 0 (process System pid) instead of 1, when parent dies.
  • Juan
    Juan about 11 years
    Beautiful....i made question before finding this answer, and if you want to post it please do so I will give you check. stackoverflow.com/q/15275983/960086
  • Aktau
    Aktau about 11 years
    Very cool, I'm really wondering if this has any reliability issues though. Have you tested this in production? With different apps?
  • Cong Ma
    Cong Ma about 11 years
    @Aktau, I've been using the Python equivalent of this trick in a Linux program. I needed it because the child's working logic is "to do best-effort cleanup after parent exits and then exit too". However, I'm really not sure about other platforms. The C snippet works on Linux and FreeBSD but that's all I know... Also, there are cases when you should be careful, such as the parent forking again, or parent giving up the fd before truly exiting (thus creating a time window for race condition).
  • Oleiade
    Oleiade over 10 years
    Nice, but five system calls, and a sh spawned in ten lines of codes lets me a bit sceptical about this piece of code performances.
  • Kamil Szot
    Kamil Szot about 10 years
    (echo -e "print(2+2)\n" & kill 0) | sh -c "python -" happily prints 4 instead of Terminated
  • Greg Hewgill
    Greg Hewgill almost 10 years
    +1. You can avoid the dup2 and taking over stdin by using the read -u flag to read from a specific file descriptor. I also added a setpgid(0, 0) in the child to prevent it from exiting when pressing ^C in the terminal.
  • Good Person
    Good Person almost 10 years
    @qrdl What is the FreeBSD equivalent ?
  • qrdl
    qrdl almost 10 years
    @GoodPerson I have no idea, never tried any BSD systems
  • dgatwood
    dgatwood about 9 years
    Unfortunately, that solution is not robust. What if the parent process dies before you get the initial value? The child will never exit.
  • Alexis Wilke
    Alexis Wilke about 9 years
    Note that a while back, under IRIX, I used a parent/child scheme where I had a pipe between both and reading from the pipe generated a SIGHUP if either one died. That was the way I used to kill my fork()'ed children, without the need for an intermediate process.
  • Alexis Wilke
    Alexis Wilke about 9 years
    @dgatwood, what do you mean?!? The first getpid() is done in the parent before calling fork(). If the parent dies before that the child does not exist. What may happen is the child out living the parent for a while.
  • Alexis Wilke
    Alexis Wilke about 9 years
    The PID equals 1 wait is not valid. The new parent could be some other PID. You should check whether it changes from the original parent (getpid() before the fork()) to the new parent (getppid() in the child not equal the getpid() when called before the fork()).
  • Alexis Wilke
    Alexis Wilke about 9 years
    The huge problem with doing the work in the parent is that you are changing the parent process. In case of a server that has to run "forever", that's not an option.
  • dgatwood
    dgatwood about 9 years
    In this somewhat contrived example, it works, but in real-world code, fork is almost invariably followed by exec, and the new process must start over by asking for its PPID. In the time between those two checks, if the parent goes away, the child would have no idea. Also, you're unlikely to have control over both the parent and child code (or else you could just pass the PPID as an argument). So as a general solution, that approach doesn't work very well. And realistically, if a UNIX-like OS came out without init being 1, so much stuff would break that I can't imagine anybody doing it anyway.
  • Maxim Egorushkin
    Maxim Egorushkin over 8 years
    This is a poor solution because the parent might have died already. Race condition. Correct solution: stackoverflow.com/a/17589555/412080
  • Nish
    Nish about 8 years
    pass parent pid is command line argument when doing exec for child.
  • maxschlepzig
    maxschlepzig almost 8 years
    Calling an answer poor isn't very nice - even if it doesn't address a race condition. See my answer on how to use prctl() in a race-condition free way. Btw, the answer linked by Maxim is incorrect.
  • maxschlepzig
    maxschlepzig almost 8 years
    The argument order of the dup2() call is wrong. If you want to use pipes[0] as stdin you have to write dup2(pipes[0], 0) instead of dup2(0, pipes[0]). It isdup2(oldfd, newfd) where the call closes a previously open newfd.
  • maxschlepzig
    maxschlepzig almost 8 years
    @Oleiade, I agree, especially since the spawned sh does just another fork to execute the real child process ...
  • maxschlepzig
    maxschlepzig almost 8 years
    Polling at full speed is insane.
  • Shalom Crown
    Shalom Crown over 7 years
    On Linux the parent PID remains the same when the parent is killed.
  • Johannes Schaub - litb
    Johannes Schaub - litb over 7 years
    I don't understand "That test can be simplified if we are certain that the grandparent is always the init process ". When a parent process dies, a process becomes a child of the init process (pid 1), not a child of the grandparent, right? So the test always seems to be correct.
  • maxschlepzig
    maxschlepzig over 7 years
  • Johannes Schaub - litb
    Johannes Schaub - litb over 7 years
    interesting, thanks. although, I fail to see what it has to do with the grandparent.
  • maxschlepzig
    maxschlepzig over 7 years
    @JohannesSchaub-litb, the grandparent sentence was sloppy - probably wanted to start with an example at that time. I updated my answer to be more generic and more explicit about the general re-parenting logic.
  • Lothar
    Lothar over 7 years
    This is just a wrong anser. It will send the signal to the child process at the time when the thread which called fork dies, not when the parent process dies.
  • qrdl
    qrdl over 7 years
    @Lothar It would be nice to see some kind of proof. man prctl says: Set the parent process death signal of the calling process to arg2 (either a signal value in the range 1..maxsig, or 0 to clear). This is the signal that the calling process will get when its parent dies. This value is cleared for the child of a fork(2) and (since Linux 2.4.36 / 2.6.23) when executing a set-user-ID or set-group-ID binary.
  • R.. GitHub STOP HELPING ICE
    R.. GitHub STOP HELPING ICE about 7 years
    I think your second caveat is wrong. The pid of a child is a resource belonging to its parent and it can't be freed/reused until the parent (the intermediate process) waits on it (or terminates and lets init wait on it).
  • Luis Colorado
    Luis Colorado about 7 years
    @neoneye, what's the interest of having a process just continously invoking getppid() to exit() once it gets a different result? Shouldn't it be possible to just call it when neccessary? Shouldn't it be better to call exit() in the child process immediately and not get a system crowded of processes that only wait for something to just call exit()?
  • Luis Colorado
    Luis Colorado about 7 years
    @JohannesSchaub-litb, you cannot always assume that the granparent of a process will be init(8) process.... the only thing you can assume is that when a parent process dies, is that its parent id will change. This actually happens once in the life of a process.... and is when the process' parent dies. There's only one main exception to this, and is for init(8) children, but you are protected from this, as init(8) never exit(2) (kernel panics in that case)
  • Alcaro
    Alcaro about 7 years
    SIGPIPE isn't sent on pipe close, it's only sent when the child tries to write to it.
  • rox
    rox about 7 years
    @maxschlepzig I can't find the answer from the link
  • maxschlepzig
    maxschlepzig about 7 years
    @rox - I don't follow. Doesn't work the link to my answer for you?
  • rox
    rox about 7 years
    @maxschlepzig Thanks for the new link. Seems like the previous link is invalid. By the way, after years, there's still no api for setting options on the parent side. What a pity.
  • rox
    rox almost 7 years
    Unfortunately, if a child forks from a thread, and then the thread exit, the child process wil get the SIGTERM.
  • Jean
    Jean almost 7 years
    Or, the child could open the lockfile in a separate thread, in blocking mode, in which case this could be a pretty nice and clean solution. Probably it has some portability limitations though.
  • Sebastien
    Sebastien over 6 years
    To have a more robust and platform independent way of doing it, before fork()-ing, simply getpid() and if getppid() from child is different, exit.
  • Omnifarious
    Omnifarious over 6 years
    @Aktau - This will be completely reliable.
  • joonas.fi
    joonas.fi about 6 years
    @SebastianJylanki I don't remember if I tried, but it probably works because the primitives (POSIX streams) are fairly standard across OSs.
  • Lucretiel
    Lucretiel almost 6 years
    This doesn't work if you don't control the child process. For instance, I'm working on a command that wraps find(1), and I want to make sure the find is killed if the wrapper dies for some reason.
  • russbishop
    russbishop almost 6 years
    You can do this with a slightly nicer API, using dispatch sources with DISPATCH_SOURCE_PROC and PROC_EXIT.
  • Darshan b
    Darshan b about 5 years
    But is polling really required. The parent can keep a track of child process ids which are returned during the process creation from fork. The parent will exit only on grace full completion of the task or because of signal. In both the cases the a signal can be sent to the child process. And the child can terminate gracefully on catching the signal. This will ensure that the response is immediate. in case of polling the cpu is wasted and the child might be alive for a longer time than the parent. further if you are using the sleep to poll then sleep is not exactly accurate.
  • Qix - MONICA WAS MISTREATED
    Qix - MONICA WAS MISTREATED almost 5 years
    For whatever reason, this is causing my Mac to panic. Running a process with this code has a 50% chance or so of it freezing, causing the fans to spin at a rate I've never heard them go before (super fast), and then the mac just shuts off. BE VERY CAREFUL WITH THIS CODE.
  • Yi Lin Liu
    Yi Lin Liu almost 5 years
    It seems like on my macOS, the child process exits automatically after parent exits. I don't know why.
  • Takagi
    Takagi almost 5 years
    @YiLinLiu iirc I used NSTask or posix spawn. See the startTask function in my code here: github.com/neoneye/newton-commander-browse/blob/master/Class‌​es/…
  • Omnifarious
    Omnifarious about 4 years
    I need to edit in another way. It's possible to use prctl to make a process act as the init process for all it's children and grandchildren, and great grandchildren, etc...
  • John Isaac
    John Isaac almost 4 years
    point of caution: systemd disables SIGPIPE's by default in services it manages, but you can still check for the pipe closure. See freedesktop.org/software/systemd/man/systemd.exec.html under IgnoreSIGPIPE
  • locka
    locka over 3 years
    This signals the child if the parent thread dies. If you spawned the child from a temporary thread then it might die prematurely. I found this out the hard way. I think the safest way, assuming you wrote the child code is to poll the parent pid and if it's not there then quit.
  • y_159
    y_159 over 3 years
    In the commented line, // continue child execution, even if do execve to execute a bash script, then also it will work(the same piece of code in the example)?
  • maxschlepzig
    maxschlepzig over 3 years
    @y_159 yes, I've covered execve in my answer. And bash isn't special here.
  • y_159
    y_159 over 3 years
    @maxschlepzig when that signal is delivered to the child when it's parent dies, the child will automatically die too?
  • maxschlepzig
    maxschlepzig over 3 years
    @y_159 depends on the signal you specify with the prctl() call. With SIGTERM (which I used in the example) the default action is to terminate. But a process could modify that action, e.g. it could even ignore it. You may also use SIGKILL there which can't be caught/ignored.
  • y_159
    y_159 over 3 years
    ok, thanks, I've used SIGTERM only as mentioned in the example, in the same way, I've not written explicitly in the child's code(a bash script in my case) to ignore any signal, so by default, it should terminate only?
  • maxschlepzig
    maxschlepzig over 3 years
    @y_159 yes, it should.
  • patraulea
    patraulea over 3 years
    Is there a way to set PDEATHSIG when starting children via posix_spawn() ?
  • qrdl
    qrdl over 3 years
    @patraulea It is responsibility of the child to request the signal to be delivered, regardless of how this child was created - using fork or posix_spawn
  • Ternvein
    Ternvein about 3 years
    @Darshanb, if you read the question carefully, there is such sentence: normally or abnormally, by kill, ^C, assert failure or anything else. Quite obviously, you can't guarantee graceful termination in such circumstances.
  • Lothar
    Lothar about 3 years
    With all the container and virutalisation methods that exist or might come in the future, it's not save to use this implementation hack from old unix systems.
  • Jonas Due Vesterheden
    Jonas Due Vesterheden almost 3 years
    @russbishop - I tried your suggestion of using a dispatch source, but it did not work for me. Here's a gist with the code I tried: gist.github.com/jdv85/5a67ae81247f21433044b0ffea404693 The event handler block does not run. Using kqueue as in the answer from @neoneye works fine.
  • russbishop
    russbishop almost 3 years
    @JonasDueVesterheden If you check the libdispatch sources it is using a kqueue under the covers so it should be identical. You don't appear to be retaining the source anywhere so it would get released and cancelled when the function exits. If you capture the source in the handler block it would resolve that problem.
  • timotheecour
    timotheecour over 2 years
    isn't CFRunLoopRun(); missing after CFRelease(source); ? similar to developer.apple.com/documentation/corefoundation/… which used CFRunLoopRunInMode(kCFRunLoopDefaultMode, 20.0, false); also, I'm not observing the panicing behavior from stackoverflow.com/questions/284325/…