Не допускати запуску дублікатів cron


92

У мене запланована робота на Cron, яку потрібно виконувати щохвилини, але іноді сценарій займає більше хвилини, і я не хочу, щоб завдання починали «складатись» один над одним. Я думаю, це проблема одночасності - тобто виконання сценарію має бути взаємовиключним.

Щоб вирішити проблему, я змусив скрипт шукати існування певного файлу (" lockfile.txt ") і вийти, якщо він існує, або touchвін є, якщо його немає. Але це досить паршивий семафор! Чи є найкраща практика, про яку я повинен знати? Чи повинен я замість цього записати демона?

Відповіді:


118

Існує декілька програм, які автоматизують цю функцію, знімають роздратування та потенційні помилки, щоб зробити це самостійно, і уникнути проблеми з блокуванням застарілих, використовуючи також флок за кадром (що є ризиком, якщо ви просто використовуєте дотик) . Я використовував lockrunі lckdoраніше, але зараз є flock(1) (у новіших версіях util-linux), що чудово. Це дуже просто у використанні:

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

2
lckdo буде видалений з moreutils, тепер, коли flock (1) знаходиться в util-linux. І цей пакет в основному є обов'язковим для систем Linux, тому ви повинні мати можливість розраховувати на його присутність. Про використання дивіться нижче.
jldugger

Так, зграя - це мій кращий варіант. Я навіть оновлю свою відповідь на задоволення.
жіночий

Хтось знає різницю між flock -n file commandі flock -n file -c command?
Нанна

2
@Nanne, я повинен був би перевірити код, щоб бути впевненим, але мій досвідчений здогад полягає в тому, що -cзапускається вказана команда через оболонку (відповідно до сторінки сторінки), тоді як "гола" (не -c) форма просто execз вказаною командою . Якщо щось розмістити через оболонку, ви можете робити подібні до оболонки речі (наприклад, виконувати кілька команд, розділених ;або &&), але також відкриває вам атаки розширення оболонки, якщо ви використовуєте ненадійний ввід.
жіноча

1
Це був аргумент (гіпотетичної) frequent_cron_jobкоманди, яка намагалася показати, що її виконують щохвилини. Я видалив його, оскільки він не додав нічого корисного і викликав плутанину (ваш, якщо ніхто більше не був за ці роки).
жіноча

28

Найкращий спосіб в оболонці - використовувати стадо (1)

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

1
Я не можу підказати хитромудрому використанню перенаправлення fd. Це просто занадто таємно дивовижно.
живіт

1
Не розбирає мене в Bash або ZSH, потрібно усунути пробіл між собою, 99і >так це99> /...
Kyle Brandt

2
@Javier: Не означає, що це не хитро і таємниче, а лише те, що це документально , хитро і таємниче.
живіт

1
що трапиться, якщо ви перезапустите, поки це запущено, або процес якось загине? Чи було б тоді заблокованим назавжди?
Alex R

5
Я розумію, що ця структура створює ексклюзивний замок, але я не розумію механіки того, як це досягнуто. Яка функція "99" у цій відповіді? Комусь хочеться пояснити це, будь ласка? Дякую!
Asciiom

22

Насправді, ви flock -nможете використовувати замість lckdo*, тому ви будете використовувати код від розробників ядра.

Спираючись на жіночий приклад , ви б написали щось на кшталт:

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

До речі, дивлячись на код, все flock, lockrunі lckdoзробити те ж саме, так що це просто питання , який є найбільш доступним для вас.


2

Можна використовувати файл блокування. Створіть цей файл під час запуску сценарію та видаліть його, коли він закінчиться. Сценарій, перш ніж запустити його основну процедуру, повинен перевірити, чи існує файл блокування, і продовжити відповідним чином.

Файли блокування використовуються за допомогою initscripts та багатьох інших програм та утиліт у системах Unix.


1
це єдиний спосіб, коли я коли-небудь бачив його реалізованим. Я використовую згідно з пропозицією керівника як дзеркало для проекту OSS
warren

2

Ви не вказали, хочете, щоб сценарій чекав завершення попереднього запуску чи ні. "Я не хочу, щоб завдання починали" складатись "один над одним", я думаю, ви маєте на увазі, що ви хочете, щоб сценарій вийшов, якщо він вже запущений,

Отже, якщо ви не хочете залежати від lckdo або подібного, ви можете зробити це:


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


Дякуємо, що ваш приклад корисний - я хочу, щоб сценарій вийшов, якщо він вже запущений. Дякуємо, що згадали про ickdo - це, здається, зробило трюк.
Том

FWIW: Мені подобається це рішення, оскільки воно може бути включено до сценарію, тому блокування працює незалежно від того, як викликається сценарій.
David G

1

Це також може бути ознакою того, що ви робите не так. Якщо ваші завдання працюють так тісно і часто, можливо, вам варто розглянути питання про деконструкцію та створення програми в стилі демон.


3
Я від цього серця не згоден. Якщо у вас є щось, що потрібно періодично запускати, зробити це демон - це рішення "кувалда для горіха". Використання файлу блокування для запобігання нещасних випадків - цілком розумне рішення, з яким у мене ніколи не виникало проблем.
живіт

@womble я згоден; але мені подобається розбивати горіхи кувалдами! :-)
wzzrd

1

Ваш крон-демон не повинен викликати завдання, якщо попередні екземпляри їх все ще запущені. Я розробник одного cron daemon dcron , і ми спеціально намагаємося цього не допустити. Я не знаю, як з цим справляються крон Віксі або інші демони.


1

Я б рекомендував використовувати команду run-one - набагато простіше, ніж мати справу з замками. З документів:

run-one - це скрипт обгортки, який виконує не більше ніж один унікальний екземпляр якоїсь команди з унікальним набором аргументів. Це часто корисно для кронштейнів, коли вам потрібно не більше однієї копії, яка працює одночасно.

run-this-one точно схожий на run-one, за винятком того, що він буде використовувати pgrep та kill для пошуку та вбивства будь-яких запущених процесів, що належать користувачеві, та відповідності цільовим командам та аргументам. Зауважте, що run-this-one буде блокуватися при спробі вбити процеси узгодження, поки всі процеси узгодження не загинуть.

run-one-постійно працює точно так само, як run-one, за винятком того, що він відновлює "COMMAND [ARGS]" в будь-який час, коли COMMAND виходить (нульовий або ненульовий).

Keep-one-running - псевдонім для запуску-один-постійно.

run-one- пока -успіх працює точно так само, як run-one-постоянно, за винятком того, що він переставляє "COMMAND [ARGS]", поки COMMAND не завершиться успішно (тобто не вийде з нуля).

run-one-till-fail працює точно так само, як run-one-постоянно, за винятком того, що він перепродає "COMMAND [ARGS]" до тих пір, поки COMMAND не завершиться з відмовою (тобто не виходить з нуля).


1

Тепер, коли systemd не працює, в системах Linux існує ще один механізм планування:

А systemd.timer

В /etc/systemd/system/myjob.serviceабо ~/.config/systemd/user/myjob.service:

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

В /etc/systemd/system/myjob.timerабо ~/.config/systemd/user/myjob.timer:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

Якщо сервісний блок вже активується при наступному активації таймера, то інший екземпляр послуги не буде запущений.

Альтернатива, яка починає роботу один раз під час завантаження та одну хвилину після кожного запуску:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target

0

Я створив одну банку, щоб вирішити таку проблему, як дублюючі крони, як, наприклад, Java або оболонка. Просто передайте ім'я cron у Duplicates.CloseSessions ("Demo.jar"), це дозволить шукати та вбивати існуючий pid для цього крона, крім поточного. Я реалізував метод для цього матеріалу. Рядок назви = ManagementFactory.getRuntimeMXBean (). GetName (); String pid = proname.split ("@") [0]; System.out.println ("Поточний 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+" ";    
                }
            }

А потім вбити рядок Killid командою знову оболонки


Я не думаю, що це справді відповідає на питання.
kasperd

0

@Philip Reynolds відповідь почне виконувати код після закінчення часу 5s, не отримуючи блокування. Слідкуючи за тим, що Флок не працює, я змінив відповідь на @Philip Reynolds

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

щоб код ніколи не виконувався одночасно. Натомість через 5 секунд очікування процес вийде з 1, якщо до цього часу він не отримав блокування.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.