Perché uno script Shell intrappola SIGTERM quando viene eseguito manualmente, ma non quando viene eseguito tramite launchd?

4

Ok, ho semplicemente uno script di shell che deve aspettare che accada qualcosa, ma ha un lock-file e alcuni processi figlio che devo assicurarmi che vengano riordinati se lo script è interrotto.

L'ho raggiunto senza problemi utilizzando il comando trap per impostare alcune azioni appropriate e ho creato uno script che assomiglia un po 'a questo:

#!/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

Questo può essere eseguito correttamente in un terminale tramite sh Example.sh e terminandolo con Ctrl + C , causando la rimozione del suo file di blocco senza problemi.

Ho quindi provato a creare un lavoro launchd per questo script in questo modo:

<?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>

Creare Example.sh e Example.plist da quanto sopra nella cartella ~/Downloads mi consente di eseguire il lavoro launchd tramite launchd load ~/Downloads/Example.plist e terminarlo tramite launchd unload ~/Downloads/Example.plist . Tuttavia, terminare il lavoro non causa un SIGTERM per raggiungere lo script, che è invece SIGKILL 'd dopo il timeout di 20 secondi.

Quindi quello che mi piacerebbe sapere è; perché il mio script non riceve SIGTERM e come posso assicurarmi che lo faccia?

    
posta Haravikk 08.03.2014 - 20:14
fonte

2 risposte

5

Il problema principale qui è che Bash normalmente non uccide i suoi figli non integrati.

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.

Quando colpisci <CTRL>+<C> stai uccidendo lo script della shell, che si comporta normalmente, ma il sonno è attivo. Usa ps per vedere.

Quando provi a fermare le cose esternamente, tramite kill , poi Bash come sopra. Dopo un certo periodo di timeout (immagino 20 secondi) launchd emette quindi un kill -9 che lo script non può intercettare.

La soluzione è di rilasciare un'attesa dopo il sonno, per indicare a Bash che può interrompere se stessa:

sleep 86400 & wait

Ciò consentirà lo script di essere interrotto, ma il sonno continuerà a sopravvivere. Sono sicuro che c'è un modo per uccidere i bambini, ma non mi sono preoccupato di cercarlo ...

    
risposta data 31.03.2014 - 15:12
fonte
4

Comprendendo che hai appena condiviso con noi un frammento di codice essenzialmente e non è chiaro quale altro demone stia cercando di ottenere effettivamente altro che eseguire un'azione ogni tanto. Quindi farò alcune ipotesi basandomi su ciò che hai scritto.

  1. Sembra che tu stia utilizzando il file di blocco per impedire l'avvio duplicato.
  2. Sembra quindi che sia necessario il trap per ripulire il file di blocco utilizzato per implementare il test per garantire la singolarità.
  3. Inoltre, sembra che il tuo deamon stia facendo un ciclo del sonno per svegliarsi periodicamente ed eseguire un'azione. (Basta dormire di più, nel tuo esempio.)

Questi sono tutti problemi che launchd intende risolvere in modi migliori con Darwin (e quindi con OS X).

Come per le domande con lo scaricamento e SIGTERM, in particolare, quando unload il tuo launchdeamon viene inviato un SIGKILL invece di un SIGTERM. Se si desidera interrompere il lavoro o inviarlo a SIGTERM, utilizzare stop anziché unload .

Se vuoi che un SIGTERM sia inviato su unload potresti aver bisogno di impostare EnableTransactions . Allo stesso modo, se hai compiti di pulizia e vuoi che i tuoi deamon ricevano i segnali per la pulizia e SIGTERM, allora devi impostare EnableTransactions come parte del launch plist per il tuo script. %codice%. Questo è descritto nei documenti all'indirizzo link

Ma i tre meccanismi di cui sopra non sono necessari dato launchd ...

Sotto Darwin / OS X utilizzando launchdaemons, il metodo appropriato per implementare un daemon del ciclo sleep consiste nell'utilizzare <key>EnableTransactions</key><true/> per l'esecuzione su un intervallo o StartInterval per l'esecuzione in base a orari specifici. L'utilizzo di StartCalendarInterval offre inoltre il vantaggio che quando il sistema è in sospensione eseguirà un intervallo di tempo mancante anziché dover attendere l'intervallo successivo ed è generalmente ciò che si desidera in queste situazioni. Se hai un lavoro che vuoi semplicemente invocare, considera anche l'utilizzo di StartCalendarInterval come parte del plist.

Quindi sembra - dall'esempio di codice che hai fornito - vuoi eseguire qualcosa ogni 86400 secondi. Se questo è il caso, launchd ha un meccanismo per fare ciò che dovresti usare e invece elimina la necessità del tuo file di lock e del trap del tutto dato che launchd è progettato per gestire tutto questo automaticamente per te. Quel meccanismo è KeepAlive e, quando definito, lancerà il tuo demone ogni N secondi. Launchd si assicura anche che non abbia lanciato più copie del demone.

Questo meccanismo è descritto nei documenti di avvio all'indirizzo link dove afferma:

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.

Quindi il tuo script di% di Darwin% co_de sembrerebbe qualcosa di molto semplice ora in questo modo:

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

E il tuo plist sarebbe simile a questo:

<?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 Ho anche regolato questo per impostare i file di registrazione qui in un modo Darwin / launchd come invece che nello script stesso. (Potresti ovviamente rimuoverli e gestirli nello script ma non è necessario dato launchd.)

Rilevo che potresti anche implementarlo utilizzando StartInterval in questo modo:

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

Potresti anche trovare link un utile riferimento oltre ai documenti Apple per come funziona launchd su link

Le informazioni sui daemon eseguite periodicamente possono essere trovate su collegamento

    
risposta data 02.04.2014 - 21:33
fonte

Leggi altre domande sui tag