¿Por qué un SIGTERM de captura de script de shell funciona cuando se ejecuta manualmente, pero no cuando se ejecuta mediante launchd?

4

De acuerdo, simplemente tengo un script de shell que debe esperar a que suceda algo, pero tiene un archivo de bloqueo y algunos procesos secundarios que debo asegurar para que se ordenen si se interrumpe el script.

Logré esto sin problemas usando el comando trap para establecer algunas acciones apropiadas, y he creado un script que se parece un poco a esto:

#!/bin/sh
LOG="$0.log"

# Create a lock-file to prevent simultaneous access
lockfile -l 86400 "$LOG.lock" || $(echo 'Locking failed' >&2 && exit 3)

# Create trap for interrupt and cleanup
on_complete() {
    echo $(date +%R)' Ended.' >> "$LOG"
    kill $(jobs -p)
    rm -f "$LOG.lock"
    exit
}
trap 'on_complete 2> /dev/null' SIGTERM SIGINT SIGHUP EXIT

# Do nothing
echo $(date +%R)' Running…' >> "$LOG"
sleep 86400 &
while wait; do sleep 86400 &; done

Esto puede ejecutarse bien en un terminal a través de sh Example.sh y terminarlo con Ctrl + C , lo que hace que elimine su archivo de bloqueo sin ningún problema.

Luego intenté crear un trabajo launchd para esta secuencia de comandos así:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.example</string>
    <key>ProgramArguments</key>
    <array>
        <string>sh</string>
        <string>~/Downloads/Example.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>EnableGlobbing</key>
    <true/>
</dict>
</plist>

La creación de Example.sh y Example.plist de lo anterior en la carpeta ~/Downloads me permite ejecutar el trabajo launchd a través de launchd load ~/Downloads/Example.plist y finalizarlo a través de launchd unload ~/Downloads/Example.plist . Sin embargo, la finalización del trabajo no hace que SIGTERM llegue a la secuencia de comandos, que en su lugar es SIGKILL 'd después del tiempo límite de 20 segundos.

Entonces, lo que me gustaría saber es; ¿Por qué mi script no recibe SIGTERM y cómo puedo asegurarme de que lo haga?

    
pregunta Haravikk 08.03.2014 - 20:14

2 respuestas

5

El problema final aquí es que Bash normalmente no mata a sus hijos no integrados.

If bash is waiting for a command to complete and receives a signal for which a
trap has been set, the trap will not be executed until the command completes.
When bash is waiting for an asynchronous command  via  the  wait  builtin, the
reception of a signal for which a trap has been set will cause the wait builtin
to return immediately with an exit status greater than 128, immediately after
which  the trap is executed.

Cuando presionas <CTRL>+<C> estás matando el script de shell, que se comporta normalmente, pero el sueño sigue vivo. Usa ps para ver.

Cuando intentes detener cosas externamente, a través de kill , luego Bash como se indica arriba. Después de un período de tiempo de espera (supongo que 20 segundos) launchd luego emite un kill -9 que el script no puede interceptar.

La solución es emitir una espera después de la suspensión, para indicar a Bash que puede interrumpirse:

sleep 86400 & wait

Esto permitirá que se interrumpa la secuencia de comandos, pero el sueño aún sobrevivirá. Estoy seguro de que hay una forma de matar a los niños, pero no me molesté en buscarlo ...

    
respondido por el Joe Casadonte 31.03.2014 - 15:12
4

Al darse cuenta de que acaba de compartir con nosotros esencialmente un fragmento de código, no está claro qué más está buscando su demonio para lograr algo más que realizar alguna acción cada tantos segundos. Así que voy a hacer algunas suposiciones basadas únicamente en lo que has escrito.

  1. Parece que estás usando el archivo de bloqueo para evitar el lanzamiento duplicado.
  2. Entonces parece que necesitas la trampa para limpiar el archivo de bloqueo utilizado para implementar tu prueba para asegurar la singularidad.
  3. Además, parece que su demonio está haciendo un ciclo de reposo para despertarse periódicamente y realizar alguna acción. (Solo duerme más, en tu ejemplo).

Estos son todos los problemas que Launchd pretende resolver de una mejor manera bajo Darwin (y, por lo tanto, OS X).

En cuanto a la (s) pregunta (s) con la descarga y SIGTERM, específicamente, cuando unload su launchdeamon recibe un SIGKILL en lugar de un SIGTERM. Si solo deseaba detener el trabajo o enviar un SIGTERM, use stop en lugar de unload .

Si desea que se envíe un SIGTERM en unload , es posible que necesite configurar EnableTransactions . Del mismo modo, si tiene tareas de limpieza y quiere que su demonio reciba señales para la limpieza y SIGTERM, entonces debe establecer EnableTransactions como parte de la lista de lanzamiento para su script. %código%. Esto se describe en los documentos en enlace

Pero los tres mecanismos anteriores son innecesarios dado el lanzamiento ...

Bajo Darwin / OS X usando launchdaemons, el método apropiado para implementar un demonio de bucle de reposo es usar <key>EnableTransactions</key><true/> para ejecutarse en un intervalo o StartInterval para ejecutar en función de momentos específicos. El uso adicional de StartCalendarInterval brinda la ventaja de que cuando el sistema está inactivo ejecutará un intervalo de tiempo perdido en lugar de tener que esperar el siguiente intervalo, y generalmente es lo que desea en estas situaciones. Si tiene un trabajo que solo desea seguir invocado, también considere usar StartCalendarInterval como parte del plist.

Parece que, según el ejemplo de código que ha proporcionado, solo desea ejecutar algo cada 86400 segundos. Si este es el caso, entonces launchd tiene un mecanismo para hacer esto que debería usar en su lugar y evita la necesidad de su archivo de bloqueo y captura, ya que launchd está diseñado para manejar todo esto de forma automática. Ese mecanismo es KeepAlive y cuando esté definido lanzará su demonio cada N segundos. Launchd también se asegura de que no haya lanzado varias copias de tu demonio.

Este mecanismo se describe en los documentos de launchd en enlace donde dice:

StartInterval <integer>
This optional key causes the job to be started every N seconds.  If the system is
asleep, the job will be started the next time the computer wakes up.  If multiple
intervals transpire before the computer is woken, those events will be coalesced 
into one event upon wake from sleep.

Por lo tanto, su secuencia de comandos de Darwin StartInterval se vería algo muy simple como ahora:

#!/bin/sh
echo $(date +%R)' Running…' # or whatever it is you wanted to do on the interval

Y tu lista se vería así:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>org.example</string>
    <key>ProgramArguments</key>
    <array>
        <string>sh</string>
        <string>~/Downloads/Example.sh</string>
    </array>
    <key>EnableGlobbing</key>
    <true/>
    <key>StartInterval</key>
    <integer>86400</integer>
    <key>StandardOutPath</key>
    <string>/mypathtolog/myjob.log</string>
    <key>StandardErrorPath</key>
    <string>/mypathtolog/myjob.log</string>
</dict>
</plist>

Nota También he ajustado esto para configurar los archivos de registro aquí de forma similar a Darwin / launchd en lugar de en el script en sí. (Por supuesto, puede eliminarlos y manejarlos en su script, pero no es necesario dado el inicio).

Observaría que también podría implementar esto usando ~/Downloads/Example.sh así:

<key>Program</key>
<string>sh</string>
<key>ProgramArguments</key>
<array>
    <string>~/Downloads/Example.sh</string>
</array>

También puede encontrar enlace como una referencia útil junto con los documentos de Apple sobre cómo launchd opera en enlace

La información sobre los demonios que se ejecutan periódicamente se puede encontrar en un

    
respondido por el ColonelMode 02.04.2014 - 21:33

Lea otras preguntas en las etiquetas