FastNetMon

Thursday, 17 June 2010

Защита от запуска нескольких инстансов (экземпляров) Perl / Python скриптов

Решение для Perl

Стоит задача - есть некий скрипт, который производит определенные действия с определенной периодичностью. Вызов скрипта в определенном случае может заблокироваться (допустим, ожиданием ввода-вывода). При этом следом может запуститься вторая копия скрипта (интервал не велик), третья и далее. Что в итоге приведет к тому, что система после разблокировки рухнет. Допустим, наш скрипт зовут: /usr/bin/ispmanager_fix_fastcgi и написан он на Перле.

Для начала алгоритм - узнаем имя текущего скрипта (ну не захардкоживать же его в самом деле), грепаем выдачу ps aux (текущие запущенные в системе процессы) на предмет наличия указанного. Если находим, то просто завершаемся - значит предыдущий запуск скрипта еще продолжает работать.

Во-первых, тут нам понадобится спец-переменная $0, которая содержит имя исполняемой программы. В моем случае это будет "/usr/bin/ispmanager_fix_fastcgi". То есть, код будет такой:
ps aux | grep "/usr/bin/ispmanager_fix_fastcgi"
Далее нам надо будет исключить из выдачи ps aux скрипт, из которго сделан запрос проверки. Тут нам поможет встроенная переменная $$, содержащая pid текущего процесса, который в пределах системы, разумеется, уникален. То есть, у нас получается нечто вида:

ps aux | grep "/usr/bin/ispmanager_fix_fastcgi" | grep -v "pid"


Также в выдачу ps aux обязательно попадет сам grep, поэтому его также нужно исключить:

ps aux | grep "/usr/bin/ispmanager_fix_fastcgi" | grep -v "pid" | grep -v grep


Итого получается следующий Perl код:

#!/usr/bin/perl

use strict;
use warnings;

unless ( system("ps aux | grep $0 | grep -v grep | grep -v $$ | grep -v '/bin/sh -c' > /dev/null") ) {
print "Duplicate!\n";
exit 0; # another copy already running
}

# тут код параллельного запуска которого мы и собираемся избегать

Исключение /bin/sh -c добавлено для Debian cron, где каждая cron задача запускается как два процесса.


Конечно, решение не подойдет для форкащихся процессов, которые имеют одинаковые имена, не подойдет оно и для скриптов запускающихся друг за другом, но если интервал между запусками велик и от него не зависят жизни людей (тут уже ключевые слова звучат, например, как "flock"), то юзать вполне можно :)

Как вариант "красивого решения", могу предложить модуль: Proc::ProcessTable, который на Debian ставится вот так:
apt-get install -y libproc-processtable-perl

Решение для Python

Логику рассуждений повторять не буду, приведу лишь код.

import os

pid = os.getpid()
Nil, scriptname = os.path.split(sys.argv[0])

result = os.system("ps aux | grep '" + scriptname + "' | grep -v grep | grep -v '" + str(pid) + "' > /dev/null" )

if result == 0:
    print "Another copy of script already running"
    sys.exit(0)

2 comments :

  1. Мутексы уже не модно чтоли ? )) как то странно дергать ps, фильтровать его вывод для таких целей...

    ReplyDelete
  2. Хз, из Перла это геморно помойму. Тут решить надо было быстро и просто :)

    ReplyDelete

Note: only a member of this blog may post a comment.