Yelp/dumb-init

Does dumb_init wait for all child processes to terminate?

richard-browne opened this issue · 9 comments

Question about SIGTERM behaviour. It's not clear from your README. Suppose I have a docker container thus:

ENTRYPOINT ["/usr/bin/dumb-init", "--", "entrypoint.sh"]

#!/bin/sh
crond -b
exec /usr/sbin/nginx -g daemon off

dumb_init will forward SIGTERM to both nginx and crond. All good. But does it wait for both nginx and crond to terminate before terminating itself? Or does it wait only for nginx to terminate? I hope the answer is that it waits for all child processes to terminate.

It will signal them to exit when the child exits, but it does not wait for them:

dumb-init/dumb-init.c

Lines 112 to 116 in 2da4dc4

if (killed_pid == child_pid) {
forward_signal(SIGTERM); // send SIGTERM to any remaining children
DEBUG("Child exited with status %d. Goodbye.\n", exit_status);
exit(exit_status);
}

Ok so it waits for its forked child, and returns its exit code right? But does not wait for any other children in the session. Did I get that right?

Please consider a feature request, to wait for all processes in the session before terminating. Then 'docker stop' behavior is as expected. IE. Wait for the container to terminate (in my example, two processes). If it doesn't terminate within a grace period, docker sends a SIGKILL.

Without waiting for all children, you have the possibility that child processes will be forcibly closed by the kernel without time to properly clean up. See here: https://stackoverflow.com/questions/39739658/what-happens-to-other-processes-when-a-docker-containers-pid1-exits.

Thank-you for considering my feature request.

Do we know if this'll be implemented at all? It's been a while since an update (to the query).

Do we know if this'll be implemented at all? It's been a while since an update (to the query).

If you have bash available in your multiprocess container then you can leverage its feature to wait on backgrounded processes. Otherwise, you could have a loop waiting on all PIDs to disappear except $$ and 1 via a trap on EXIT function.

If you don’t have bash and instead have a minimal docker container you could add in statically compiled bash. I have some write ups if there’s any interest on docker practices but they don’t specifically cover this edge case.

If you have bash available in your multiprocess container then you can leverage its feature to wait on backgrounded processes.

I have some write ups if there’s any interest on docker practices but they don’t specifically cover this edge case.

Heya, the first part certainly sounds like my current use case.

Effectively: Alpine w/ Bash, process being called by Crond, but if the container calls an INT or similar signal, finding that the child process doesn't receive the signal.

We've tried tini, but it's only good as a zombie reaper which doesn't quite cover the use case (borgmatic-collective/docker-borgmatic#173)

This works for us btw:

NUM_PROCESSES=${NUM_PROCESSES:-1}
if [ "${NUM_PROCESSES}" -gt 1 ]; then
  # See: https://github.com/Yelp/dumb-init#session-behavior
  # Here we use `dumb-init` to start a subshell which will run all
  # the processes and wait for them to exit/finish.
  # `dumb-init` will forward all signals to these processes and
  # `wait -n` will make sure to exit if at least one process exits prematurely.
  exec /usr/bin/dumb-init -- /bin/bash -c "for run in \$(seq ${NUM_PROCESSES}); do $* & done; \
                                           trap 'wait' SIGINT SIGTERM; \
                                           wait -n"
fi
mitar commented

I made another init system for Docker, dinit, which tries to address exactly this: having multiple children processes, sending signal to them when the container (PID 1) gets the signal, and then waiting for all to finish. If some process does not finish, then it is left to the container's supervisor (i.e., Docker) to kill all processes.

@mitar dinit looks like exactly what we need. Surprised this hasn't happened before now. I think not many people appreciate the nuances of init with docker.

mitar commented

@richard-browne: Yea, I was also surprised that something like that does not exist yet. Especially once you realize that there are design differences when operating in a container (e.g., shutting down the whole container when one process dies so that its supervisor is informed about the death and then supervisor restarts the container if necessary, sending only TERM to processes and leaving KILL to the supervisor). Anyway, feel free to test it out and open any issues/discussions there, so that we do not fill this issue more.