Ogólne rozwiązanie zapobiegające równoległemu działaniu długiego zadania CRON?

27

Szukam prostego i ogólnego rozwiązania, które umożliwiłoby wykonanie dowolnego skryptu lub aplikacji w crontab i zapobiegnięcie dwukrotnemu uruchomieniu.

Rozwiązanie powinno być niezależne od wykonanego polecenia.

Zakładam, że powinno to wyglądać tak, lock && (command ; unlock)gdzie zamek zwróci wartość false, jeśli był inny zamek.

Druga część byłaby taka, jakby uzyskała blokadę, uruchom polecenie i odblokuj po wykonaniu polecenia, nawet jeśli zwróci błąd.

sorin
źródło

Odpowiedzi:

33

Spójrz na pakiet run-oneZainstaluj Run-One . Z strony podręcznika dla run-onepoleceniaIkona strony :

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.

Podobnie jak timelub sudo, po prostu wstawiasz go do polecenia. Tak więc koleżanka może wyglądać następująco:

  */60 * * * *   run-one rsync -azP $HOME example.com:/srv/backup

Aby uzyskać więcej informacji i kontekstu, sprawdź post na blogu, który przedstawia Dustin Kirkland.

andrews coś
źródło
5

Bardzo prosty sposób ustawienia blokady:

if mkdir /var/lock/mylock; then
  echo "Locking succeeded" >&2
else
  echo "Lock failed - exit" >&2
  exit 1
fi

Skrypty, które chcesz uruchomić, muszą utworzyć blokadę. Jeśli blokada istnieje, inny skrypt jest zajęty, więc pierwszy skrypt nie może zostać uruchomiony. Jeśli plik nie istnieje, żaden skrypt nie uzyskał blokady. Zatem bieżący skrypt przejmuje blokadę. Po zakończeniu skryptu blokada musi zostać ponownie zwolniona przez usunięcie blokady.

Aby uzyskać więcej informacji o blokadach bash, sprawdź stronę

OrangeTux
źródło
1
Będziesz także potrzebował pułapki WYJŚCIA, która usuwa blokadę przy wyjściu. echo "Locking succeeded" >&2; trap 'rm -rf /var/lock/mylock' EXIT
geirha
1
Idealnie byłoby użyć stada doradczego z procesu, który uruchamia polecenie, które chcesz jako podzadanie. W ten sposób, jeśli oni wszyscy umierają stado zostaje zwolniony automatycznie, który korzystając z obecności pliku blokady nie robią. Korzystanie z portu sieciowego działałoby w podobny sposób - choć jest to o wiele mniejsza przestrzeń nazw, co stanowi problem.
Alex North-Keys,
3

Nie musisz instalować jakiegoś fantazyjnego pakietu:

#!/bin/bash
pgrep -xf "$*" > /dev/null || "$@"

Szybciej jest napisać ten skrypt sam, niż uruchomić „apt-get install”, prawda? Możesz dodać „-u $ (id -u)” do pgrep, aby sprawdzić wystąpienia uruchomione tylko przez bieżącego użytkownika.

Michael Kowhan
źródło
2
nie gwarantuje to pojedynczego wystąpienia. dwa skrypty mogą przejść jednocześnie na drugą stronę ||operatora, zanim którykolwiek z nich będzie miał jeszcze możliwość uruchomienia skryptu.
Sedat Kapanoglu
@SedatKapanoglu To prawda, że ​​ten skrypt nie jest odporny na warunki wyścigowe, ale pierwotne pytanie dotyczyło długotrwałych zadań cron (uruchamianych najwyżej raz na minutę). Jeśli Twój system potrzebuje więcej niż minutę na utworzenie procesu, masz inne problemy. Jednak w razie potrzeby z innych powodów możesz użyć flock (1), aby zabezpieczyć powyższy skrypt przed warunkami wyścigowymi.
Michael Kowhan,
Użyłem tego, ale dla skryptu bash, który powinien się sprawdzić. Kod jest następujący: v = $ (pgrep -xf "/ bin / bash $ 0 $ @") ["$ {v / $ BASHPID /}"! = ""] && exit 2
ahofmann
3

Zobacz także Tim Kay solo, który wykonuje blokowanie, wiążąc port na adres pętli zwrotnej unikalny dla użytkownika:

http://timkay.com/solo/

W przypadku awarii jego strony:

Stosowanie:

solo -port=PORT COMMAND

where
    PORT        some arbitrary port number to be used for locking
    COMMAND     shell command to run

options
    -verbose    be verbose
    -silent     be silent

Użyj tego w ten sposób:

* * * * * solo -port=3801 ./job.pl blah blah

Scenariusz:

#!/usr/bin/perl -s
#
# solo v1.7
# Prevents multiple cron instances from running simultaneously.
#
# Copyright 2007-2016 Timothy Kay
# http://timkay.com/solo/
#
# It is free software; you can redistribute it and/or modify it under the terms of either:
#
# a) the GNU General Public License as published by the Free Software Foundation;
#    either version 1 (http://dev.perl.org/licenses/gpl1.html), or (at your option)
#    any later version (http://www.fsf.org/licenses/licenses.html#GNUGPL), or
#
# b) the "Artistic License" (http://dev.perl.org/licenses/artistic.html), or
#
# c) the MIT License (http://opensource.org/licenses/MIT)
#

use Socket;

alarm $timeout                              if $timeout;

$port =~ /^\d+$/ or $noport                     or die "Usage: $0 -port=PORT COMMAND\n";

if ($port)
{
    # To work with OpenBSD: change to
    # $addr = pack(CnC, 127, 0, 1);
    # but make sure to use different ports across different users.
    # (Thanks to  www.gotati.com .)
    $addr = pack(CnC, 127, $<, 1);
    print "solo: bind ", join(".", unpack(C4, $addr)), ":$port\n"   if $verbose;

    $^F = 10;           # unset close-on-exec

    socket(SOLO, PF_INET, SOCK_STREAM, getprotobyname('tcp'))       or die "socket: $!";
    bind(SOLO, sockaddr_in($port, $addr))               or $silent? exit: die "solo($port): $!\n";
}

sleep $sleep if $sleep;

exec @ARGV;
DNA
źródło
W systemie Mac OSX błąd solo(3801): Can't assign requested addressnie powiedzie się, chyba że wymusisz a 0dla trzeciego parametru metody pack. To, co jest dobre dla BSD, jest również dobre dla komputerów Mac.
Eric Leschinski
1

Potrzebujesz blokady. run-onewykonuje zadanie, ale możesz również zajrzeć do niego flockz util-linuxpaczki.

Jest to standardowy pakiet dostarczany przez programistów jądra, pozwala na większą personalizację niż run-onei jest nadal bardzo prosty.

mucha styropianowa
źródło
0

Prostym rozwiązaniem z bash-hackers.org, które działało dla mnie, było użycie mkdir . Jest to prosty sposób, aby upewnić się, że działa tylko jedna instancja programu. Utwórz katalog z mkdir .lock, który zwraca

  • prawda, jeśli stworzenie się powiodło i
  • false, jeśli plik blokady istnieje, wskazując, że obecnie działa jedna instancja.

Więc ta prosta funkcja zrobiła całą logikę blokowania plików:

if mkdir .lock; then
    echo "Locking succeeded"
    eval startYourProgram.sh ;
else
    echo "Lock file exists. Program already running? Exit. "
    exit 1
fi


echo "Program finished, Removing lock."
rm -r .lock
domih
źródło
0

To rozwiązanie dotyczy skryptu bash, który musi się sprawdzić

v=$(pgrep -xf "/bin/bash $0 $@")
[ "${v/$BASHPID/}" != "" ] && exit 0
ahofmann
źródło