2

I have a daemon application that starts several 3rd party executables (all closed-sources and non modifiable).

I would like to have all the child processes to automatically terminate when the parent exits for any reason (including crashes).

Currently, I am using prctl to achieve this (see also this question):

int ret = fork();
if (ret == 0) {
    //Setup other stuff
    prctl (PR_SET_PDEATHSIG, SIGKILL);

    if (execve( "childexecutable" ) < 0) { /*signal error*/}
}

However, if "childexecutable" also forks and spawns "grandchildren", then "grandchildren" is not killed when my process exits.

Maybe I could create an intermediate process that serves as subreaper, that would then kill "someexecutable" when my process dies, but then wait for SIGCHLD and continue to kill child processes until none is left, but it seems very brittle.

Are there better solutions?

sbabbi
  • 11,070
  • 2
  • 29
  • 57
  • If you're using standard POSIX and C tools, there's no sensible way to find out the child processes of a process (even the current process). On Linux, you can pull stunts by analyzing the `/proc` file system. For PID 19383, you can look in `/proc/19383/task/19383/children`, I think. – Jonathan Leffler Aug 02 '21 at 17:47
  • Does this answer your question? [How to kill a process tree programmatically on Linux using C](https://stackoverflow.com/questions/15692275/how-to-kill-a-process-tree-programmatically-on-linux-using-c) – Joshua Aug 02 '21 at 17:47
  • I probably need to emphasize the "including crashes" part. Finding the children and killing them it's not a big problem. The problem is to do it even after the "root" process crashes. The `prctl` trick works even if the root process segfaults. While `kill` is signal safe, writing this much logic within a SIGSEGV handler seems tricky. – sbabbi Aug 02 '21 at 18:24

1 Answers1

2

Creating a subreaper is not useful in this case, your grandchildren would be reparented to and reaped by init anyway.

What you could do however is:

  1. Start a parent process and fork a child immediately.
  2. The parent will simply wait for the child.
  3. The child will carry out all the work of your actual program, including spawning any other children via fork + execve.
  4. Upon exit of the child for any reason (including deathly signals e.g. a crash) the parent can issue kill(0, SIGKILL) or killpg(getpgid(0), SIGKILL) to kill all the processes in its process group. Issuing a SIGINT/SIGTERM before SIGKILL would probably be a better idea depending on what child processes you want to run, as they could handle such signals and do a graceful cleanup of used resources (including children) before exiting.

Assuming that none of the children or grandchildren changes their process group while running, this will kill the entire tree of processes upon exit of your program. You could also keep the PR_SET_PDEATHSIG before any execve to make this more robust. Again depending on the processes you want to run a PR_SET_PDEATHSIG with SIGINT/SIGTERM could make more sense than SIGKILL.

You can issue setpgid(getpid(), 0) before doing any of the above to create a new process group for your program and avoid killing any parents when issuing kill(0, SIGKILL).

The logic of the "parent" process should be really simple, just a fork + wait in a loop + kill upon the right condition returned by wait. Of course, if this process crashes too then all bets are off, so take care in writing simple and reliable code.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • You can also replace the call to `setpgid()` with a call to [`setsid()`](https://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html) to start a new process group and it will also detach from the controlling terminal. I'm not sure why that answer was deleted. – Andrew Henle Aug 02 '21 at 19:03
  • The semantics of the two are different, don't know which one OP would actually need, but `setpgid()` is the bare minimum to make this work, while `setsid()` might not be wanted in some scenarios. – Marco Bonelli Aug 02 '21 at 19:04