Запускать задание cron, только если оно еще не запущено

136

Итак, я пытаюсь настроить задание cron в качестве своего рода сторожевого пса для созданного мной демона. Если демон выдает ошибку и выходит из строя, я хочу, чтобы задание cron периодически перезапускало его ... Я не уверен, насколько это возможно, но я прочитал пару руководств по cron и не смог найти ничего, что могло бы делать то, что я ищу ...

Мой демон запускается из сценария оболочки, поэтому я просто ищу способ запустить задание cron, ТОЛЬКО если предыдущий запуск этого задания еще не выполняется.

Я нашел этот пост , который предлагает решение того, что я пытаюсь сделать с помощью файлов блокировки, не я не уверен, есть ли лучший способ сделать это ...

Спасибо за вашу помощь.

LorenVS
источник

Ответы:

120

Я делаю это для программы диспетчера очереди печати, которую я написал, это просто сценарий оболочки:

#!/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

Он запускается каждые две минуты и довольно эффективен. У меня есть электронное письмо со специальной информацией, если по какой-то причине процесс не запущен.

jjclarkson
источник
4
не очень безопасное решение, но что, если есть другой процесс, который соответствует поиску, который вы выполнили в grep? Ответ rsanden предотвращает подобную проблему с использованием pidfile.
Элиас Дорнелес
12
Это колесо уже где-то изобрели :) Например, serverfault.com/a/82863/108394
Filipe Correia
5
Вместо grep -v grep | grep doctype.phpтебя можно сделать grep [d]octype.php.
AlexT
Обратите внимание, что в этом нет необходимости, &если скрипт запускает cron.
lainatnavi
125

Использование flock. Это новое. Лучше.

Теперь вам не нужно писать код самостоятельно. Ознакомьтесь с другими причинами здесь: https://serverfault.com/a/82863

/usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script
Джесс
источник
1
Очень простое решение
MFB
4
Лучшее решение, я использую его очень давно.
soger
1
Я также создал здесь хороший шаблон cron: gist.github.com/jesslilly/315132a59f749c11b7c6
Джесс
3
setlock, s6-setlock, chpst, И runlockв их неблокирующих режимах альтернативы, которые доступны на более , чем просто Linux. unix.stackexchange.com/a/475580/5132
JdeBP
3
Я считаю, что это должен быть принятый ответ. Так просто!
Codemonkey
62

Как утверждали другие, запись и проверка файла PID - хорошее решение. Вот моя реализация на 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
источник
3
+1 Использование pidfile, вероятно, намного безопаснее, чем grepping для запущенной программы с тем же именем.
Элиас Дорнелес
/ путь / к / myprogram &> $ HOME / tmp / myprogram.log & ?????? Возможно, вы имели в виду / path / to / myprogram >> $ HOME / tmp / myprogram.log &
matteo
1
Не следует ли удалить файл после завершения скрипта? Или мне не хватает чего-то очень очевидного?
Hamzahfrq
1
@matteo: Да, ты прав. Я исправил это в своих заметках много лет назад, но забыл обновить его здесь. Хуже того, я тоже пропустил это в вашем комментарии, заметив только >«против >>». Извини за это.
rsanden
5
@Hamzahfrq: Вот как это работает: сценарий сначала проверяет, существует ли файл PID (" [ -e "${PIDFILE}" ]". Если нет, то он запускает программу в фоновом режиме, записывает свой PID в файл (" echo $! > "${PIDFILE}"") и exit. Если вместо этого PID-файл существует, то сценарий проверит ваши собственные процессы (" ps -u $(whoami) -opid=") и увидит, запущен ли вы один с тем же PID (" grep -P "^\s*$(cat ${PIDFILE})$""). Если нет, то он запустит программа, как и раньше, перезапишите файл PID новым идентификатором PID и выйдите. Я не вижу причин изменять сценарий; не так ли?
rsanden
35

Удивительно, что про run-one никто не упомянул . Я решил с этим свою проблему.

 apt-get install run-one

затем добавьте run-oneперед скриптом crontab

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

Посмотрите этот ответ на askubuntu SE. Вы также можете найти ссылку на подробную информацию.

Беди Эгильмез
источник
22

Не пытайтесь делать это через cron. Пусть cron запустит скрипт, что бы ни случилось, а затем пусть скрипт решит, запущена ли программа, и запустит ее при необходимости (обратите внимание, что для этого вы можете использовать Ruby или Python или ваш любимый язык сценариев)

Earlz
источник
5
Классический способ - прочитать файл PID, который служба создает при запуске, проверить, работает ли процесс с этим идентификатором PID, и перезапустить его, если нет.
tvanfosson 02
9

Вы также можете сделать это как однострочник прямо в вашем crontab:

* * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>
quezacoatl
источник
5
не очень безопасно, что делать, если есть другие команды, которые соответствуют поиску grep?
Элиас Дорнелес
1
Это также можно было бы записать как * * * * * [ ps -ef|grep [c]ommand-eq 0] && <command>, где перенос первой буквы вашей команды в скобки исключает ее из результатов grep.
Джим Клаус
Мне пришлось использовать следующий синтаксис:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
thameera
1
Это отвратительно [ $(grep something | wc -l) -eq 0 ]это действительно окольный способ писать ! grep -q something. Так что вы хотите простоps -ef | grep '[c]ommand' || command
tripleee
(Кроме того, если вы действительно хотите подсчитать количество совпадающих строк, то это grep -c.)
tripleee
7

Я делаю это, когда запускаю скрипты php:

Кронтаб:

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

Код php:

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

Эта команда ищет в списке системных процессов текущее имя файла php, если оно существует, счетчик строк (wc -l) будет больше единицы, потому что сама команда поиска содержит имя файла

поэтому, если вы запускаете php crons, добавьте указанный выше код в начало вашего php-кода, и он будет запускаться только один раз.

talsibony
источник
Это то, что мне было нужно, так как все другие решения требовали установки чего-то на клиентском сервере, к чему у меня нет доступа.
Джефф Дэвис
5

В продолжение ответа Эрлза вам понадобится сценарий-оболочка, который при запуске создает файл $ PID.running и удаляет его по окончании. Скрипт-оболочка вызывает скрипт, который вы хотите запустить. Оболочка необходима в случае сбоя или ошибки целевого скрипта, файл pid удаляется.

Байрон Уитлок
источник
О, круто ... Я никогда не думал об использовании оболочки ... Я не мог придумать, как это сделать с помощью файлов блокировки, потому что я не мог гарантировать, что файл будет удален, если демон выйдет из строя ... A Обертка будет работать отлично, я собираюсь попробовать решение jjclarkson, но я сделаю это, если это не сработает ...
LorenVS 02
3

Я бы рекомендовал использовать существующий инструмент, такой как monit , он будет отслеживать и автоматически перезапускать процессы. Существует более подробная информация доступна здесь . Он должен быть легко доступен в большинстве дистрибутивов.

Zitrax
источник
Каждый ответ, кроме этого, отвечает на поверхностный вопрос: «Как мое задание cron может убедиться, что оно запускает только один экземпляр?» когда реальный вопрос звучит так: «Как я могу сохранить работу моего процесса после перезапуска?», и правильный ответ действительно - не использовать cron, а вместо этого использовать супервизор процесса, такой как monit. Другие варианты включают runit , s6 или, если ваш дистрибутив уже использует systemd, просто создание службы systemd для процесса, который необходимо поддерживать.
clacke
3

Этот меня никогда не подводил:

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}

cron работа :

* * * * * /path/to/one.sh <command>
DJV
источник
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
}

ОБНОВЛЕНИЕ .. лучший способ использовать flock:

/usr/bin/flock -n /tmp/your-app.lock /path/your-app args 
ekerner
источник
1

Я бы предложил следующее в качестве улучшения ответа rsanden (я бы опубликовал как комментарий, но у меня недостаточно репутации ...):

#!/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

Это позволяет избежать возможных ложных совпадений (и накладных расходов на grepping), а также подавляет вывод и полагается только на статус выхода ps.

dbenton
источник
1
Ваш ps команда будет соответствовать PID для других пользователей в системе, а не только для вашего собственного. Добавление " -u" к psкоманде изменяет способ работы статуса выхода.
rsanden
1

Достаточно простого настраиваемого php. Не нужно путать со сценарием оболочки.

Предположим, вы хотите запустить php /home/mypath/example.php, если он не запущен

Затем используйте следующий настраиваемый скрипт php для выполнения той же работы.

создать следующий /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);
    }
?>

Затем в вашем cron добавьте следующее

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'
lingeshram
источник
0

Если вы собираетесь пойти по этому пути, подумайте об использовании pgrep (если доступно), а не ps, передаваемого через grep. Хотя лично у меня много пробега из скриптов вида

while(1){
  call script_that_must_run
  sleep 5
}

Хотя это может дать сбой и хроны рабочих мест являются часто лучшим способом для основных вещей. Еще одна альтернатива.

Ричард Томас
источник
2
Это просто запустит демон снова и снова и не решит проблему, упомянутую выше.
cwoebker
0

Документы: https://www.timkay.com/solo/

solo - это очень простой сценарий (10 строк), который не позволяет программе запускать более одной копии за раз. С cron полезно убедиться, что задание не запускается до завершения предыдущего.

пример

* * * * * solo -port=3801 ./job.pl blah blah
Эрланг П
источник