Uruchom zadanie cron tylko wtedy, gdy nie jest jeszcze uruchomione

143

Próbuję ustawić pracę cron jako swego rodzaju stróża dla demona, którego stworzyłem. Jeśli demon wystąpi i zawiedzie, chcę, aby zadanie crona okresowo go restartowało ... Nie jestem pewien, jak to jest możliwe, ale przeczytałem kilka samouczków crona i nie mogłem znaleźć niczego, co zrobiłoby to, co ja Szukam ...

Mój demon uruchamia się ze skryptu powłoki, więc tak naprawdę szukam sposobu na uruchomienie zadania cron TYLKO, jeśli poprzednie uruchomienie tego zadania nadal nie działa.

Znalazłem ten post , który zapewnił rozwiązanie tego, co próbuję zrobić za pomocą plików blokujących, nie jestem pewien, czy istnieje lepszy sposób na zrobienie tego ...

LorenVS
źródło

Odpowiedzi:

121

Robię to dla programu bufora wydruku, który napisałem, to tylko skrypt powłoki:

#!/bin/sh
if ps -ef | grep -v grep | grep doctype.php ; then
        exit 0
else
        /home/user/bin/doctype.php >> /home/user/bin/spooler.log &
        #mailing program
        /home/user/bin/simplemail.php "Print spooler was not running...  Restarted." 
        exit 0
fi

Działa co dwie minuty i jest dość skuteczny. Mam go e-mailem ze specjalnymi informacjami, jeśli z jakiegoś powodu proces nie działa.

jjclarkson
źródło
4
niezbyt bezpieczne rozwiązanie, ale co, jeśli istnieje inny proces, który pasuje do wyszukiwania przeprowadzonego w grep? Odpowiedź rsandena zapobiega tego rodzaju problemom przy użyciu pliku pid.
Elias Dorneles
14
To koło zostało już wynalezione gdzie indziej :) Np. Serverfault.com/a/82863/108394
Filipe Correia
5
Zamiast tego grep -v grep | grep doctype.phpmożesz zrobić grep [d]octype.php.
AlexT
Zauważ, że nie ma potrzeby, aby &skrypt był uruchamiany przez crona.
lainatnavi
134

Użyj flock. Jest nowe. To jest lepsze.

Teraz nie musisz samodzielnie pisać kodu. Sprawdź więcej powodów tutaj: https://serverfault.com/a/82863

/usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script
Pęto
źródło
1
Bardzo łatwe rozwiązanie
MFB
4
Najlepsze rozwiązanie, używam tego od naprawdę długiego czasu.
soger
1
Stworzyłem również ładny fragment
Jess,
3
setlock, s6-setlock, chpst, I runlockw swoich trybach non-blocking są alternatywy, które są dostępne na więcej niż tylko Linux. unix.stackexchange.com/a/475580/5132
JdeBP
3
Uważam, że to powinna być akceptowana odpowiedź. Tak prosty!
Codemonkey
63

Jak stwierdzili inni, pisanie i sprawdzanie pliku PID jest dobrym rozwiązaniem. Oto moja implementacja bash:

#!/bin/bash

mkdir -p "$HOME/tmp"
PIDFILE="$HOME/tmp/myprogram.pid"

if [ -e "${PIDFILE}" ] && (ps -u $(whoami) -opid= |
                           grep -P "^\s*$(cat ${PIDFILE})$" &> /dev/null); then
  echo "Already running."
  exit 99
fi

/path/to/myprogram > $HOME/tmp/myprogram.log &

echo $! > "${PIDFILE}"
chmod 644 "${PIDFILE}"
rsanden
źródło
3
+1 Używanie pliku pid jest prawdopodobnie dużo bezpieczniejsze niż grepowanie uruchomionego programu o tej samej nazwie.
Elias Dorneles
/ path / to / myprogram &> $ HOME / tmp / myprogram.log & ?????? czy może miałeś na myśli / path / to / myprogram >> $ HOME / tmp / myprogram.log &
matteo
1
Czy plik nie powinien zostać usunięty po zakończeniu działania skryptu? A może brakuje mi czegoś bardzo oczywistego?
Hamzahfrq
1
@matteo: Tak, masz rację. Naprawiłem to w moich notatkach lata temu, ale zapomniałem zaktualizować tutaj. Gorzej, brakowało mi tego również w twoim komentarzu, zauważając tylko " >" kontra " >>". Przepraszam za to.
rsanden
5
@Hamzahfrq: Oto jak to działa: Skrypt najpierw sprawdza, czy plik PID istnieje („ [ -e "${PIDFILE}" ]”. Jeśli tak nie jest, uruchomi program w tle, zapisze swój PID do pliku („ echo $! > "${PIDFILE}"”) i zakończ. Jeśli zamiast tego plik PID istnieje, skrypt sprawdzi Twoje własne procesy („ ps -u $(whoami) -opid=”) i sprawdzi, czy używasz jednego z tym samym PID („ grep -P "^\s*$(cat ${PIDFILE})$"”). Jeśli nie, skrypt rozpocznie program jak poprzednio, nadpisz plik PID nowym PID i zakończ. Nie widzę powodu do modyfikowania skryptu; czy ty?
rsanden
35

Zaskakujące jest to, że nikt nie wspomniał o run-one . Rozwiązałem ten problem.

 apt-get install run-one

następnie dodaj run-oneprzed skryptem crontab

*/20 * * * * * run-one python /script/to/run/awesome.py

Sprawdź odpowiedź askubuntu SE. Znajdziesz tam również link do szczegółowych informacji.

Bedi Egilmez
źródło
22

Nie próbuj tego robić przez cron. Niech cron uruchomi skrypt bez względu na wszystko, a następnie niech skrypt zdecyduje, czy program jest uruchomiony i uruchom go, jeśli to konieczne (pamiętaj, że możesz do tego użyć Ruby, Python lub ulubionego języka skryptowego)

Earlz
źródło
5
Klasycznym sposobem jest odczytanie pliku PID, który usługa tworzy podczas uruchamiania, sprawdzenie, czy proces z tym PIDem nadal działa, a jeśli nie, ponowne uruchomienie.
tvanfosson
9

Możesz to również zrobić jako jednolinijkowy bezpośrednio w swojej tabeli crontab:

* * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>
quezacoatl
źródło
5
niezbyt bezpieczne, a jeśli istnieją inne polecenia, które pasują do wyszukiwania grep?
Elias Dorneles
1
Można to również zapisać jako * * * * * [ ps -ef|grep [c]ommand-eq 0] && <polecenie>, gdzie zawijanie pierwszej litery polecenia w nawiasach wyklucza je z wyników grep.
Jim Clouse,
Musiałem użyć następującej składni:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
thameera
1
To jest ohydne. [ $(grep something | wc -l) -eq 0 ]to naprawdę okrężny sposób pisania ! grep -q something. Więc chcesz po prostups -ef | grep '[c]ommand' || command
tripleee
(Na marginesie, jeśli naprawdę chciałeś policzyć liczbę pasujących wierszy, to grep -c
znaczy
7

Sposób, w jaki to robię, gdy uruchamiam skrypty php, to:

Crontab:

* * * * * php /path/to/php/script.php &

Kod php:

<?php
if (shell_exec('ps aux | grep ' . __FILE__ . ' | wc  -l') > 1) {
    exit('already running...');
}
// do stuff

To polecenie wyszukuje na liście procesów systemowych bieżącą nazwę pliku php, jeśli istnieje, licznik linii (wc -l) będzie większy niż jeden, ponieważ samo polecenie wyszukiwania zawiera nazwę pliku

więc jeśli korzystasz z php cronów, dodaj powyższy kod na początku swojego kodu php i uruchomi się tylko raz.

talsibony
źródło
Właśnie tego potrzebowałem, ponieważ wszystkie inne rozwiązania wymagały zainstalowania czegoś na serwerze klienta, do czego nie mam dostępu.
Jeff Davis,
5

W następstwie odpowiedzi Earlz, potrzebujesz skryptu opakowującego, który tworzy plik $ PID.running po uruchomieniu i usuwa po jego zakończeniu. Skrypt opakowania wywołuje skrypt, który chcesz uruchomić. Opakowanie jest niezbędne na wypadek awarii lub błędu skryptu docelowego, plik pid zostanie usunięty.

Byron Whitlock
źródło
O super ... Nigdy nie myślałem o użyciu wrappera ... Nie mogłem znaleźć sposobu, aby to zrobić przy użyciu plików blokujących, ponieważ nie mogłem zagwarantować, że plik zostanie usunięty, jeśli demon popełni błąd ... wrapper działałby idealnie, zamierzam spróbować rozwiązania jjclarkson, ale zrobię to, jeśli to nie zadziała ...
LorenVS
3

Polecam użycie istniejącego narzędzia, takiego jak monit , będzie ono monitorować i automatycznie restartować procesy. Więcej informacji można znaleźć tutaj . Powinien być łatwo dostępny w większości dystrybucji.

Zitrax
źródło
Każda odpowiedź oprócz tej odpowiada na powierzchowne pytanie „W jaki sposób moje zadanie cron może mieć pewność, że działa tylko jedna instancja?” kiedy prawdziwe pytanie brzmi: „Jak mogę utrzymać proces w stanie pracy w obliczu ponownego uruchomienia?”, a prawidłowa odpowiedź brzmi: nie używać crona, ale zamiast tego nadzorującego proces, takiego jak monit. Inne opcje obejmują runit , s6 lub, jeśli twoja dystrybucja już używa systemd, po prostu tworzenie usługi systemd dla procesu, który musi być żywy.
clacke
3

Ten nigdy mnie nie zawiódł:

one.sh :

LFILE=/tmp/one-`echo "$@" | md5sum | cut -d\  -f1`.pid
if [ -e ${LFILE} ] && kill -0 `cat ${LFILE}`; then
   exit
fi

trap "rm -f ${LFILE}; exit" INT TERM EXIT
echo $$ > ${LFILE}

$@

rm -f ${LFILE}

praca cron :

* * * * * /path/to/one.sh <command>
DJV
źródło
3
# one instance only (works unless your cmd has 'grep' in it)
ALREADY_RUNNING_EXIT_STATUS=0
bn=`basename $0`
proc=`ps -ef | grep -v grep | grep "$bn" | grep -v " $$ "`
[ $? -eq 0 ] && {
    pid=`echo $proc | awk '{print $2}'`
    echo "$bn already running with pid $pid"
    exit $ALREADY_RUNNING_EXIT_STATUS
}

UPDATE ... lepszy sposób przy użyciu flocka:

/usr/bin/flock -n /tmp/your-app.lock /path/your-app args 
ekerner
źródło
1

Sugerowałbym następujące ulepszenie odpowiedzi rsandena (opublikowałbym jako komentarz, ale nie mam wystarczającej reputacji ...):

#!/usr/bin/env bash

PIDFILE="$HOME/tmp/myprogram.pid"

if [ -e "${PIDFILE}" ] && (ps -p $(cat ${PIDFILE}) > /dev/null); then
  echo "Already running."
  exit 99
fi

/path/to/myprogram

Pozwala to uniknąć możliwych fałszywych dopasowań (i narzutu greppingu) i blokuje wyjście i polega tylko na statusie wyjścia ps.

dbenton
źródło
1
Twoje pspolecenie dopasuje PID dla innych użytkowników w systemie, nie tylko dla Twojego. Dodanie „ -u” do pspolecenia zmienia sposób działania statusu wyjścia.
rsanden
1

Wystarczy prosty niestandardowy php do osiągnięcia. Nie trzeba mylić ze skryptem powłoki.

załóżmy, że chcesz uruchomić php /home/mypath/example.php, jeśli nie jest uruchomiony

Następnie użyj następującego niestandardowego skryptu php, aby wykonać to samo zadanie.

utwórz następujący /home/mypath/forever.php

<?php
    $cmd = $argv[1];
    $grep = "ps -ef | grep '".$cmd."'";
    exec($grep,$out);
    if(count($out)<5){
        $cmd .= ' > /dev/null 2>/dev/null &';
        exec($cmd,$out);
        print_r($out);
    }
?>

Następnie w swoim cronie dodaj następujące

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'
lingeshram
źródło
0

Rozważ użycie pgrep (jeśli jest dostępne) zamiast ps piped przez grep, jeśli zamierzasz iść tą drogą. Chociaż osobiście mam dużo kilometrów ze skryptów formularza

while(1){
  call script_that_must_run
  sleep 5
}

Chociaż może to zawieść, a zadania cron często najlepszym sposobem na podstawowe rzeczy. Po prostu inna alternatywa.

Richard Thomas
źródło
2
To po prostu uruchomiłoby demona w kółko i nie rozwiązałoby problemu wspomnianego powyżej.
cwoebker
0

Dokumenty: https://www.timkay.com/solo/

solo to bardzo prosty skrypt (10 linii), który uniemożliwia programowi uruchomienie więcej niż jednej kopii na raz. Przydaje się w cronie, aby upewnić się, że zadanie nie zostanie uruchomione przed zakończeniem poprzedniego.

Przykład

* * * * * solo -port=3801 ./job.pl blah blah
Erlang P.
źródło