Zapobiegaj uruchamianiu zduplikowanych zadań cron

92

Zaplanowałem uruchamianie zadania cron co minutę, ale czasami skrypt trwa dłużej niż minutę i nie chcę, aby zadania zaczęły się „nakładać” na siebie. Myślę, że jest to problem współbieżności - tzn. Wykonywanie skryptu musi się wykluczać.

Aby rozwiązać problem, sprawiłem, że skrypt szukał określonego pliku („ lockfile.txt ”) i kończy działanie, jeśli istnieje, lub touchjeśli nie. Ale to kiepski semafor! Czy jest jakaś najlepsza praktyka, o której powinienem wiedzieć? Czy zamiast tego powinienem napisać demona?

Tomek
źródło

Odpowiedzi:

118

Istnieje kilka programów, które automatyzują tę funkcję, usuwają irytację i potencjalne błędy związane z robieniem tego samemu, a także unikają problemu przestarzałej blokady, używając również stada za sceną (co jest ryzykowne, jeśli używasz tylko dotyku) . Kiedyś używałem lockruni lckdokiedyś, ale teraz jest flock(1) (w nowych wersjach util-linux), co jest świetne. Jest naprawdę łatwy w użyciu:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job
womble
źródło
2
lckdo zostanie usunięty z moreutils, teraz, gdy flock (1) jest w Linuksie. Pakiet ten jest w zasadzie obowiązkowy w systemach Linux, więc powinieneś móc polegać na jego obecności. Aby zapoznać się z użytkowaniem, spójrz poniżej.
jldugger
Tak, stado jest teraz moją preferowaną opcją. Zaktualizuję nawet moją odpowiedź, aby pasowała.
womble
Czy ktoś zna różnicę między flock -n file commandi flock -n file -c command?
Nanne
2
@Nanne, musiałbym sprawdzić kod, aby się upewnić, ale zgaduję, że -curuchamia określone polecenie przez powłokę (jak na stronie podręcznika), podczas gdy -cforma „goła” (nie- ) jest tylko execpoleceniem podanym . Przekazanie czegoś przez powłokę umożliwia wykonywanie czynności podobnych do powłoki (takich jak uruchamianie wielu poleceń oddzielonych za pomocą ;lub &&), ale także otwiera cię na ataki rozszerzania powłoki, jeśli używasz niezaufanego wejścia.
womble
1
Był to argument dla (hipotetycznego) frequent_cron_jobpolecenia, które próbowało pokazać, że jest uruchamiane co minutę. Usunąłem go, ponieważ nie dodał nic użytecznego i spowodował zamieszanie (twoje, jeśli nikt inny nie jest przez lata).
womble
28

Najlepszym sposobem w powłoce jest użycie stada (1)

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock
Philip Reynolds
źródło
1
Nie mogę nie głosować za trudnym użyciem przekierowania fd. To po prostu zbyt niesamowicie niesamowite.
womble
1
Nie analizuje dla mnie w Bash ani ZSH, muszę wyeliminować odstęp między nimi 99i >tak jest99> /...
Kyle Brandt
2
@Javier: Nie znaczy to, że nie jest podstępny i tajemny, tylko że jest udokumentowany , podstępny i tajemny.
womble
1
co by się stało, jeśli zrestartujesz się, gdy jest uruchomiony, lub jakoś zabijesz proces? Czy byłby wtedy zamknięty na zawsze?
Alex R
5
Rozumiem, że ta struktura tworzy wyłączną blokadę, ale nie rozumiem mechaniki tego, jak to się robi. Jaka jest funkcja „99” w tej odpowiedzi? Czy ktoś chciałby to wyjaśnić, proszę? Dzięki!
Asciiom
22

Właściwie flock -nmoże być użyte zamiast lckdo*, więc będziesz używać kodu od programistów jądra.

Opierając się na przykładzie womble , napiszesz coś takiego:

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

BTW, patrząc na kod, wszystko flock, lockruni lckdorobić dokładnie to samo, więc to tylko kwestia co jest najłatwiej dostępne.

Amir
źródło
2

Możesz użyć pliku blokady. Utwórz ten plik po uruchomieniu skryptu i usuń go po zakończeniu. Skrypt, zanim uruchomi swoją główną procedurę, powinien sprawdzić, czy plik blokady istnieje i postępować odpowiednio.

Pliki blokujące są używane przez skrypty startowe oraz wiele innych aplikacji i narzędzi w systemach uniksowych.

Urodzony by jeździć
źródło
1
jest to jedyny sposób, w jaki widziałem to osobiście. Używam zgodnie z sugestią opiekuna jako zwierciadło dla projektu OSS
warren
2

Nie określiłeś, czy chcesz, aby skrypt czekał na zakończenie poprzedniego uruchomienia, czy nie. Poprzez „Nie chcę, aby zadania zaczęły się„ nakładać ”jeden na drugi, myślę, że sugerujesz, że chcesz, aby skrypt został zamknięty, jeśli jest już uruchomiony,

Więc jeśli nie chcesz polegać na lckdo lub podobnym, możesz to zrobić:


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

Aleksandar Ivanisevic
źródło
Dzięki, twój przykład jest pomocny - chcę, żeby skrypt się zakończył, jeśli jest już uruchomiony. Dzięki za wspomnienie o ickdo - wydaje się, że to załatwi sprawę.
Tom
FWIW: Podoba mi się to rozwiązanie, ponieważ można je włączyć do skryptu, więc blokowanie działa niezależnie od sposobu wywołania skryptu.
David G
1

Może to również oznaczać, że robisz coś złego. Jeśli twoje zadania działają tak blisko i tak często, być może powinieneś rozważyć usunięcie ich z programu i uczynienie z niego programu w stylu demona.


źródło
3
Serdecznie się z tym nie zgadzam. Jeśli masz coś, co musi być uruchamiane okresowo, uczynienie go demonem jest rozwiązaniem „młota dla orzecha”. Używanie pliku blokady w celu zapobiegania wypadkom jest całkowicie rozsądnym rozwiązaniem, z którego nigdy nie miałem problemu.
womble
@womble Zgadzam się; ale lubię rozbijać orzechy młotami! :-)
wzzrd
1

Twój demon cron nie powinien wywoływać zadań, jeśli poprzednie instancje nadal działają. Jestem programistą jednego demona cron dcron i staramy się temu zapobiec. Nie wiem, jak Vixie cron lub inne demony sobie z tym radzą.

dubiousjim
źródło
1

Poleciłbym użycie polecenia run-one - o wiele prostsze niż radzenie sobie z blokadami. Z dokumentów:

run-one to skrypt otoki, który uruchamia nie więcej niż jedno unikalne wystąpienie jakiegoś polecenia z unikalnym zestawem argumentów. Jest to często przydatne w cronjobs, gdy nie chcesz, aby działała więcej niż jedna kopia na raz.

run-this-one jest dokładnie tak samo jak run-one, z tym wyjątkiem, że użyje pgrep i kill do znalezienia i zabicia wszelkich uruchomionych procesów należących do użytkownika i dopasowania poleceń i argumentów docelowych. Zauważ, że run-this-one blokuje się podczas próby zabicia pasujących procesów, dopóki wszystkie pasujące procesy nie zostaną zakończone.

run-one-stale działa dokładnie tak, jak run-one, z tym wyjątkiem, że odradza się „COMMAND [ARGS]” za każdym razem, gdy kończy się polecenie COMMAND (zero lub zero).

keep-one-running to alias do ciągłego uruchamiania.

run-one-dopóki-sukces działa dokładnie tak, jak run-one-stale, z tym wyjątkiem, że odradza się „COMMAND [ARGS]”, dopóki COMMAND nie zakończy się pomyślnie (tzn. zakończy zero).

run-one- till -failure działa dokładnie tak, jak run-one-stale, z tym wyjątkiem, że odradza się „COMMAND [ARGS]”, dopóki COMMAND nie zakończy się niepowodzeniem (tzn. zakończy się niezerowo).

Jurik
źródło
1

Teraz, gdy systemd jest już dostępny, istnieje inny mechanizm planowania w systemach Linux:

ZA systemd.timer

W /etc/systemd/system/myjob.servicelub ~/.config/systemd/user/myjob.service:

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

W /etc/systemd/system/myjob.timerlub ~/.config/systemd/user/myjob.timer:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

Jeśli jednostka serwisowa już aktywuje się, gdy następny timer zostanie aktywowany, to kolejne wystąpienie usługi nie zostanie uruchomione.

Alternatywa, która uruchamia zadanie raz podczas rozruchu i minutę po zakończeniu każdego uruchomienia:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target
Amir
źródło
0

Stworzyłem jeden słoik, aby rozwiązać taki problem, jak uruchomione duplikaty cronów, może to być cron java lub shell. Wystarczy podać nazwę crona w Duplicates.CloseSessions („Demo.jar”), aby wyszukać i zabić istniejący pid dla tego crona oprócz bieżącej. Wdrożyłem metodę, aby to zrobić. Zaimek ciągu = ManagementFactory.getRuntimeMXBean (). GetName (); Ciąg pid = proname.split („@”) [0]; System.out.println („Current PID:” + 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+" ";    
                }
            }

A następnie zabij killid string ponownie poleceniem powłoki

Sachin Patil
źródło
Nie sądzę, że to naprawdę odpowiada na pytanie.
kasperd
0

Odpowiedź Philipa Reynoldsa rozpocznie wykonywanie kodu po upływie czasu oczekiwania 5 sekund bez uzyskania blokady. Obserwowanie Flocka wydaje się nie działać. Zmodyfikowałem odpowiedź @Philip Reynolds na

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

aby kod nigdy nie był wykonywany jednocześnie. Zamiast tego po upływie 5 sekund proces zakończy się na 1, jeśli do tego czasu nie uzyska blokady.

użytkownik__42
źródło