dumb-init icon indicating copy to clipboard operation
dumb-init copied to clipboard

Does dumb_init wait for all child processes to terminate?

Open richard-browne opened this issue 4 years ago • 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.

richard-browne avatar Oct 13 '20 22:10 richard-browne

It will signal them to exit when the child exits, but it does not wait for them: https://github.com/Yelp/dumb-init/blob/2da4dc4591eff242e1aeeed7d94b00252bd1512b/dumb-init.c#L112-L116

chriskuehl avatar Oct 13 '20 22:10 chriskuehl

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.

richard-browne avatar Oct 13 '20 22:10 richard-browne

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

modem7 avatar Nov 08 '22 22:11 modem7

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.

samrocketman avatar Nov 09 '22 02:11 samrocketman

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 (https://github.com/borgmatic-collective/docker-borgmatic/pull/173)

modem7 avatar Nov 09 '22 02:11 modem7

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

komapa avatar Feb 09 '23 21:02 komapa

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 avatar Jul 14 '23 13:07 mitar

@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.

richard-browne avatar Jul 20 '23 00:07 richard-browne

@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.

mitar avatar Jul 20 '23 06:07 mitar