Why do sleep & wait in bash?

2020-05-20 08:10发布

问题:

I'm having trouble understanding the startup commands for the services in this docker-compose.yml. The two relevant lines from the .yml are:

command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"

and

entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

Why send the sleep command to the background and then wait on it? Why not just do sleep 6h directly? Also, is the double dollar sign just escaping the dollar sign in ${!}?

I'm finding other places where sleep and wait are used in conjunction, but none seem to have any explanation of why:

  1. http://www.masteringunixshell.net/qa17/bash-how-to-wait-seconds.html
  2. https://stackoverflow.com/a/13301329/828584
  3. https://superuser.com/a/753984/98583

回答1:

It makes sense to sleep in background and then wait, when one wants to handle signals in a timely manner.

When bash is executing an external command in the foreground, it does not handle any signals received until the foreground process terminates

(detailed explanation here).

While the second example implements a signal handler, for the first one it makes no difference whether the sleep is executed in foreground or not. There is no trap and the signal is not propagated to the nginx process. To make it respond to the SIGTERM signal, the entrypoint should be something this:

/bin/sh -c 'nginx -g \"daemon off;\" & trap exit TERM; while :; do sleep 6h  & wait $${!}; nginx -s reload; done'

To test it:

docker run --name test --rm --entrypoint="/bin/sh" nginx  -c 'nginx -g "daemon off;" & trap exit TERM; while :; do sleep 20 & wait ${!}; echo running; done'

Stop the container

docker stop test

or send the TERM signal (docker stop sends a TERM followed by KILL if the main process does not exit)

docker kill --signal=SIGTERM test

By doing this, the scripts exits immediately. Now if we remove the wait ${!} the trap is executed when sleep ends. All that works well for the second example too.

Note: in both cases the intention is to check certificate renewal every 12h and reload the configuration every 6h as mentioned in the guide The two commands do that just fine. IMHO the additional wait in the first example is just an oversight of the developers.

EDITED:

It seems the rationalization above, which was meant to give possible reasons behind the background sleep, might create some confusion. (There is a related post Why use nginx with “daemon off” in background with docker?).

While the command suggested in the answer above is an improvement over the one in the question it is still flawed because, as mentioned in the linked post, the nginx server should be the main process and not a child. That can be easily achieved using the exec system call. The script becomes:

'while :; do sleep 6h; nginx -s reload; done & exec nginx -g "daemon off;"'

(More info in section Configure app as PID 1 in Docker best practices)

This, IMHO, is far better because not only is nginx monitored but it also handle signals. A configuration reload (nginx -s reload), for example, can also be done manually by simply sending the HUP signal to the docker container (See Controlling nginx).



回答2:

The only reason I see:

If you killall -INT sleep, this won't affect main script.

Try this:

while true ;do sleep 12; echo yes;done

Then send a Interrupt signal:

killall -INT sleep

This will break the job!

Try now

while true ;do sleep 12 & wait $! ; echo yes;done

Then again:

killall -INT sleep

Job won't break!

Sample output, hitting killall -INT sleep from another window:

user@myhost:~$ while true ;do sleep 12; echo yes;done
break

user@myhost:~$ while true ;do sleep 12 & wait $! ; echo yes;done
[1] 30632
[1]+  Interrupt               sleep 12
yes
[1] 30636
[1]+  Interrupt               sleep 12
yes
[1] 30638
[1]+  Interrupt               sleep 12
yes
[1] 30640


标签: bash docker