Использование модулей File::Pid, Pid::File::Flock и File::Flock::Tiny. Блокировки файлов, работа с pid-файлами. Работа только одной копии скрипта в один момент времени. Защита от повторного запуска одного и того же скрипта, до того, как первый экземпляр завершит свою работу.
Допустим, что есть perl-скрипт, который забирает данные с удаленных серверов. Или разносит данные из одного источника в таблицы БД. Скрипт работает достаточно долго, от получаса и более - зависит от объема данных, скорости ответа удаленных серверов и т.п. Запускается по cron, раз в час.
Во избежание проблем с дублированием данных, взаимными блокировками в БД и т.п., необходимо чтобы скрипт всегда был запущен только в одном экземпляре.
Для этого можно использовать pid-файл. При запуске, скрипт проверяет наличие pid-файла. Если файл доступен, то блокирует его и сохраняет в файле свой PID, и только после этого приступает к выполнению своей основной работы. Если файл существует, но доступ к нему заблокирован - скрипт сразу же завершает свою работу. Если pid-файл не существует, то скрипт создает его, блокирует, сохраняет свой pid, и приступает к основной работе.
Для того, чтобы не изобретать велосипед, можно найти на cpan модули для работы с pid-файлами. В простых приложениях их будет вполне достаточно.
File::Flock::Tiny
File::Flock::Tiny - очень удобный модуль для работы с pid-файлами.
Установка проходит без проблем.
1 |
# cpan install File::Flock::Tiny |
Пример использования, pid2.pl:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/perl use strict; use File::Flock::Tiny; my $pid = File::Flock::Tiny->write_pid('script.pid') or do { print "Script already running"; exit(0); }; sleep(60); # имитируем длительную работу скрипта exit; |
Сохраняем скрипт и запускаем его. После этого будет создан файл script.pid с ID процесса внутри.
1 |
# perl pid2.pl |
Смотрим список процессов, убедиться, что скрипт работает:
1 2 |
# ps -u root -f root 31487 30877 0 02:32 pts/1 00:00:00 perl pid2.pl |
После этого, пытаемся в другом окне терминала запустить тот же самый скрипт, не дожидаясь окончания работы первого экземпляра:
1 2 |
# perl pid2.pl Script already running |
Методы File::Flock::Tiny
File::Flock::Tiny->lock($file)
Эксклюзивная блокировка файла. $file - может быть как именем файла, так и файловым дескриптором. Файл блокируется до того момента, пока процесс не выйдет из границ функции, в которой блокировка была установлена. Либо, можно вызвать метод разблокировки объекта. Если указанный файл не существовал - он будет создан.
Если скрипт был повторно запущен до того момента, как будет снята блокировка файла, второй процесс будет ожидать снятия блокировки и после этого продолжит работу, в свою очередь, захватив файл.
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/perl use strict; use File::Flock::Tiny; my $lock = File::Flock::Tiny->lock('script.txt'); $lock->write_pid; exit; |
File::Flock::Tiny->trylock($file)
Делает попытку заблокировать файл, если не получилось - возвращает undef.
1 2 3 4 |
my $lock = File::Flock::Tiny->trylock('script.txt') or do { print "File already blocked: $$"; exit(0); }; |
File::Flock::Tiny->write_pid($file)
Делает попытку заблокировать указанный файл и сохранить в него ID текущего процесса. Если попытка удалась - возвращает ссылку на заблокированный объект, если нет - вернет undef. После завершения процесса файл не удаляется, но блокировка будет снята. При повторном выполнении файл будет вновь заблокирован, ID процесса в файле - обновлен.
$lock->write_pid
Удаляет все данные из заблокированного файла, если они были, и сохраняет в него ID текущего процесса.
$lock->release
Снимает блокировку и закрывает файл.
$lock->close
Метод закрывает файловый дескриптор, но блокировку с файла не снимает! Может быть использовано, если есть порожденные дочерние процессы, которым блокировка еще для чего-то будет нужна.
Однако, данный метод работает не на всех операционных системах, поэтому, во имя переносимости программ - его лучше не использовать.
Pid::File::Flock
Пример использования, pid4.pl:
1 2 3 4 5 6 7 8 |
#!/usr/bin/perl use strict; use Pid::File::Flock; Pid::File::Flock->new( name => "script3.pid" , dir => "." )->abandon; exit; |
Создает или открывает уже существующий pid-файл, устанавливает блокировку, сохраняет в него ID процесса. После завершения работы блокировка снимается. Файл не удаляется.
При попытке запустить второй экземпляр скрипта, пока еще не завершил работу первый и с pid-файла не была снята блокировка, выдает ошибку:
1 2 |
# perl pid4.pl found alive process (32403), exit at pid4.pl line 7. |
Методы Pid::File::Flock
new( $path, %options )
$path - необязательный аргумент, который применяется, если параметры 'dir','name' and 'ext' по каким-либо причинам не будут использованы.
Параметры:
- dir - директория, в которой будет создан pid-файл. Если не указано, то будет вызван File::Spec::tmpdir - который создаст файл в каком-нибудь каталоге /temp . Лучше указывать.
- name - имя pid-файла.
- ext - расширение для pid-файла. По умолчанию будет использоваться '.pid'.
- debug => 1 - включает режим отладки. Начнет выдавать дополнительную информацию через STDERR.
- quiet => 1 - включает "тихий" режим. Не выдает предупреждений об устаревших pid-файлах.
abandon
Указание не пытаться удалить файл в процессе завершения работы. Если abandon не использовать, pid-файл будет удален после завершения работы скрипта.
File::Pid
Попробовала использовать File::Pid для блокировки запуска скрипта. Не получилось.
1 |
cpan install File::Pid |
Пример, pid.pl:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
use File::Pid; my $pid = File::Pid->new({ file => 'script.pid', }); if ( my $num = $pid->running ) { die "Script already running: $num\n"; } $pid->write; $pid->remove; |
Сразу же возникли проблемы:
- если файл с pid еще не существует (а откуда ему взяться, при первом запуске скрипта) - выводится ошибка.
123# perl pid.plCan't kill a non-numeric process ID at/usr/local/share/perl/5.14.2/File/Pid.pm line 124. - если файл с pid создан, но сам pid в нем не записан - ошибка
- если файл создан, и pid в нем указан, то pid текущего процесса в нем не сохраняется. Старый и уже не существующий pid продолжает сохраняться.
1# perl pid.pl
Содержимое script.pid:
133000
Реальный процесс:
12# ps -u root -froot 31114 30877 0 01:11 pts/1 00:00:00 perl pid.pl
До проверки, действительно ли можно предотвратить повторный запуск скрипта с помощью
File::Pid не дошло. Не вижу смысла. Возможно, данный модуль стал не актуальным с течением времени, написан с ошибками (что более вероятно), и имеет проблемы с использованием на разных операционных системах.
Вспомнила, что File::Pid использовался в одной из систем, которую я встречала года 3 назад. Запуск скриптов там всегда был проблемой. Либо не запускались вообще, либо запускались в нескольких экземплярах. Теперь понятно - почему. Не рекомендую.
Спасибо, интересно.
Если процесс падает где-нибудь в середине, блокировка снимается?
У меня везде есть подключение к memcached или redis, я там ставлю признак блокировки с expire. Если процесс упал и не удалил за собой lock, тогда он умрет сам по себе через какое-то время.
Зависит от того, какой модуль используется для проверки. Те, которые я приводила в пример, проверяют наличие блокировки и самого процесса в начале работы. Если процесса уже нет - то "блокировка" не действительна. В самом деле, там не используется блокировка на уровне операционной системы, когда я зашла и подредактировала pid-файл во время работы процесса, с помощью mc - мне система и слова не сказала :) Так что, если предполагается чей-то злонамеренный доступ к pid-файлам, надо перед внедрением такой системы, хорошенько ее проверить.