Impedisci l'esecuzione di lavori cron duplicati


92

Ho pianificato l'esecuzione di un processo cron ogni minuto, ma a volte lo script impiega più di un minuto per terminare e non voglio che i lavori inizino a "impilarsi" l'uno sull'altro. Immagino che si tratti di un problema di concorrenza, vale a dire che l'esecuzione dello script deve escludersi a vicenda.

Per risolvere il problema ho fatto in modo che lo script cercasse l'esistenza di un determinato file (" lockfile.txt ") e uscissi se esiste o touchse non lo è. Ma questo è un semaforo piuttosto schifoso! Esiste una best practice che dovrei conoscere? Avrei dovuto invece scrivere un demone?

Risposte:


118

Ci sono un paio di programmi che automatizzano questa funzione, eliminano il fastidio e i potenziali bug dal farlo da soli ed evitano il problema del blocco stantio usando anche il gregge dietro le quinte (il che è un rischio se stai solo usando il tocco) . Ho usato lockrune lckdoin passato, ma ora c'è flock(1) (nelle versioni newish di util-linux) che è fantastico. È davvero facile da usare:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job

2
lckdo verrà rimosso da moreutils, ora che flock (1) è in util-linux. E quel pacchetto è sostanzialmente obbligatorio nei sistemi Linux, quindi dovresti essere in grado di fare affidamento sulla sua presenza. Per l'utilizzo, guarda sotto.
jldugger,

Sì, ora il gregge è la mia opzione preferita. Aggiornerò anche la mia risposta per adattarla.
womble

Qualcuno sa la differenza tra flock -n file commande flock -n file -c command?
Nanne,

2
@Nanne, dovrei controllare il codice per essere sicuro, ma la mia ipotesi istruita è che -cesegue il comando specificato attraverso una shell (come per la manpage), mentre la forma "nuda" (non -c) è solo execil comando dato . Mettere qualcosa attraverso la shell ti permette di fare cose simili alla shell (come eseguire più comandi separati da ;o &&), ma ti apre anche agli attacchi di espansione della shell se stai usando input non attendibili.
womble

1
Era un argomento al frequent_cron_jobcomando (ipotetico) che cercava di dimostrare che veniva eseguito ogni minuto. L'ho rimosso dal momento che non ha aggiunto nulla di utile e ha causato confusione (la tua, se nessun altro è stato nel corso degli anni).
Womble

28

Il modo migliore in shell è usare flock (1)

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock

1
Non posso non votare un uso complicato del reindirizzamento fd. È semplicemente incredibilmente fantastico.
womble

1
Non mi analizza in Bash o ZSH, devo eliminare lo spazio tra 99e >così è99> /...
Kyle Brandt

2
@Javier: Non significa che non sia complicato e arcano, ma solo che è documentato , complicato e arcano.
womble

1
cosa accadrebbe se si riavvia mentre questo è in esecuzione o se il processo viene ucciso in qualche modo? Sarebbe bloccato per sempre allora?
Alex R,

5
Capisco che questa struttura crea un blocco esclusivo, ma non capisco i meccanismi di come questo viene realizzato. Qual è la funzione del '99' in questa risposta? Qualcuno vuole spiegarlo per favore? Grazie!
Asciiom,

22

In realtà, flock -npuò essere usato al posto di lckdo*, quindi userai il codice dagli sviluppatori del kernel.

Sulla base l'esempio di Womble , si potrebbe scrivere qualcosa di simile:

* * * * * flock -n /some/lockfile command_to_run_every_minute

BTW, guardando il codice, tutti flock, lockrune lckdofare la stessa cosa, quindi è solo una questione di che è più facilmente disponibile per voi.


2

È possibile utilizzare un file di blocco. Crea questo file all'avvio dello script ed eliminalo al termine. Lo script, prima di eseguire la sua routine principale, dovrebbe verificare se esiste il file di blocco e procedere di conseguenza.

I lockfile sono utilizzati da initscripts e da molte altre applicazioni e utilità nei sistemi Unix.


1
questo è l' unico modo in cui l'ho mai visto implementato, personalmente. Uso il suggerimento del manutentore come specchio per un progetto OSS
warren

2

Non è stato specificato se si desidera che lo script attenda il completamento dell'esecuzione precedente o meno. Con "Non voglio che i lavori inizino a" impilarsi "l'uno sull'altro", suppongo che tu stia insinuando che vuoi che lo script esca se è già in esecuzione,

Quindi, se non vuoi dipendere da lckdo o simili, puoi farlo:


PIDFILE=/tmp/`basename $0`.pid

if [ -f $PIDFILE ]; then
  if ps -p `cat $PIDFILE` > /dev/null 2>&1; then
      echo "$0 already running!"
      exit
  fi
fi
echo $$ > $PIDFILE

trap 'rm -f "$PIDFILE" >/dev/null 2>&1' EXIT HUP KILL INT QUIT TERM

# do the work


Grazie, il tuo esempio è utile: voglio che lo script esca se è già in esecuzione. Grazie per aver menzionato ickdo - sembra fare il trucco.
Tom,

FWIW: Mi piace questa soluzione perché può essere inclusa in uno script, quindi il blocco funziona indipendentemente da come viene invocato lo script.
David G,

1

Questo potrebbe anche essere un segno che stai facendo la cosa sbagliata. Se i tuoi lavori vengono eseguiti così da vicino e così frequentemente, forse dovresti considerare di desincronizzarlo e renderlo un programma in stile demone.


3
Non sono assolutamente d'accordo con questo. Se hai qualcosa che deve essere eseguito periodicamente, renderlo un demone è una soluzione "mazza per una noce". L'uso di un file di blocco per prevenire incidenti è una soluzione perfettamente ragionevole che non ho mai avuto problemi a utilizzare.
womble

@womble Sono d'accordo; ma mi piace distruggere le mazze con le mazze! :-)
wzzrd

1

Il tuo demone cron non dovrebbe invocare lavori se le precedenti istanze sono ancora in esecuzione. Sono lo sviluppatore di un cron daemon dcron e cerchiamo specificamente di impedirlo. Non so come gestiscono Vixie cron o altri demoni.


1

Consiglierei di usare il comando run-one - molto più semplice rispetto ai blocchi. Dai documenti:

run-one è uno script wrapper che non esegue più di un'istanza univoca di alcuni comandi con un set univoco di argomenti. Questo è spesso utile con cronjobs, quando non si desidera eseguire più di una copia alla volta.

run-this-one è esattamente come run-one, tranne per il fatto che utilizzerà pgrep e kill per trovare e uccidere tutti i processi in esecuzione di proprietà dell'utente e corrispondenti ai comandi e agli argomenti di destinazione. Si noti che run-this-one si bloccherà durante il tentativo di uccidere i processi di matching, fino a quando tutti i processi di matching non saranno morti.

run-one-costantemente funziona esattamente come run-one, tranne per il fatto che rigenererà "COMMAND [ARGS]" ogni volta che COMMAND esce (zero o diverso da zero).

keep-one-running è un alias per run-one-costantemente.

run-one-until-success funziona esattamente come run-one-costantemente, tranne per il fatto che rilancia "COMMAND [ARGS]" fino a quando COMMAND non esce correttamente (ovvero, esce da zero).

run-one-until-failure funziona esattamente come run-one-costantemente, tranne per il fatto che risorge "COMMAND [ARGS]" fino a quando COMMAND non esce con esito negativo (ovvero, esce da zero).


1

Ora che systemd è fuori, c'è un altro meccanismo di pianificazione sui sistemi Linux:

UN systemd.timer

In /etc/systemd/system/myjob.serviceo ~/.config/systemd/user/myjob.service:

[Service]
ExecStart=/usr/local/bin/myjob

In /etc/systemd/system/myjob.timero ~/.config/systemd/user/myjob.timer:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

Se l'unità di servizio si sta già attivando alla successiva attivazione del timer, non verrà avviata un'altra istanza del servizio .

Un'alternativa, che avvia il processo una volta all'avvio e un minuto dopo il completamento di ogni esecuzione:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target

0

Ho creato un barattolo per risolvere questo problema come i croni duplicati in esecuzione potrebbero essere java o shell cron. Basta passare il nome cron in Duplicates.CloseSessions ("Demo.jar") questo cercherà e ucciderà il pid esistente per questo cron tranne corrente. Ho implementato il metodo per fare queste cose. String proname = ManagementFactory.getRuntimeMXBean (). GetName (); String pid = proname.split ("@") [0]; System.out.println ("PID corrente:" + pid);

            Process proc = Runtime.getRuntime().exec(new String[]{"bash","-c"," ps aux | grep "+cronname+" | awk '{print $2}' "});

            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String s = null;
            String killid="";

            while ((s = stdInput.readLine()) != null ) {                                        
                if(s.equals(pid)==false)
                {
                    killid=killid+s+" ";    
                }
            }

E poi uccidi la stringa killid con di nuovo il comando shell


Non penso che questo stia davvero rispondendo alla domanda.
Kasperd,

0

La risposta di @Philip Reynolds inizierà comunque ad eseguire il codice dopo il tempo di attesa 5s senza ottenere il blocco. Dopo che Flock non sembra funzionare, ho modificato la risposta di @Philip Reynolds

(
  flock -w 5 -x 99 || exit 1
  ## Do your stuff here
) 99>/path/to/my.lock

in modo che il codice non venga mai eseguito contemporaneamente. Invece dopo 5 secondi attendere il processo terminerà con 1 se non ha ottenuto il blocco da allora.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.