В настоящее время я пытался предотвратить onlytask.php
сценария onlytask.php
более одного раза:
$fp = fopen("/tmp/"."onlyme.lock", "a+"); if (flock($fp, LOCK_EX | LOCK_NB)) { echo "task started\n"; // while (true) { // do something lengthy sleep(10); } // flock($fp, LOCK_UN); } else { echo "task already running\n"; } fclose($fp);
и есть работа cron для выполнения вышеупомянутого скрипта каждую минуту:
* * * * * php /usr/local/src/onlytask.php
Это работает некоторое время. Через несколько дней, когда я это сделаю:
ps auxwww | grep onlytask
Я обнаружил, что есть два экземпляра! Не три или более, не один. Я убил один из экземпляров. Через несколько дней снова повторяются два примера.
Что не так в коде? Существуют ли другие альтернативы для ограничения только одного экземпляра onlytask.php?
ps my /tmp/
папка не очищается. ls -al /tmp/*.lock
показывает, что файл блокировки был создан в первый день:
-rw-r--r-- 1 root root 0 Dec 4 04:03 onlyme.lock
При открытии файла блокировки вы должны использовать флаг x
:
<?php $lock = '/tmp/myscript.lock'; $f = fopen($lock, 'x'); if ($f === false) { die("\nCan't acquire lock\n"); } else { // Do processing while (true) { echo "Working\n"; sleep(2); } fclose($f); unlink($lock); }
Примечание из руководства по PHP
' x ' – создавать и открывать только для записи; поместите указатель файла в начало файла. Если файл уже существует, вызов fopen () завершится неудачей, возвратив FALSE и создав ошибку уровня E_WARNING. Если файл не существует, попытайтесь его создать. Это эквивалентно определению флагов O_EXCL | O_CREAT для базового открытого (2) системного вызова.
И вот объяснение O_EXCL
странице man :
O_EXCL – если O_CREAT и O_EXCL установлены, open () должен завершиться неудачно, если файл существует. Проверка наличия файла и создания файла, если он не существует, должен быть атомарным относительно других потоков, выполняющих open (), именовав то же имя файла в том же каталоге, что и O_EXCL и O_CREAT. Если O_EXCL и O_CREAT установлены, а пути – символическая ссылка, open () завершится с ошибкой и установите errno на [EEXIST], независимо от содержимого символической ссылки. Если O_EXCL установлен и O_CREAT не установлен, результат не определен.
ОБНОВЛЕНИЕ :
Более надежный подход – запустить основной скрипт, который получает блокировку, запускает рабочий сценарий и освобождает блокировку.
<?php // File: main.php $lock = '/tmp/myscript.lock'; $f = fopen($lock, 'x'); if ($f === false) { die("\nCan't acquire lock\n"); } else { // Spawn worker which does processing (redirect stderr to stdout) $worker = './worker 2>&1'; $output = array(); $retval = 0; exec($worker, $output, $retval); echo "Worker exited with code: $retval\n"; echo "Output:\n"; echo implode("\n", $output) . "\n"; // Cleanup the lock fclose($f); unlink($lock); }
Здесь идет рабочий. Давайте поднимем фатальную ошибку:
#!/usr/bin/env php <?php // File: worker (must be executable +x) for ($i = 0; $i < 3; $i++) { echo "Processing $i\n"; if ($i == 2) { // Fake fatal error trigger_error("Oh, fatal error!", E_USER_ERROR); } sleep(1); }
Вот результат, который я получил:
galymzhan@atom:~$ php main.php Worker exited with code: 255 Output: Processing 0 Processing 1 Processing 2 PHP Fatal error: Oh, fatal error! in /home/galymzhan/worker on line 8 PHP Stack trace: PHP 1. {main}() /home/galymzhan/worker:0 PHP 2. trigger_error() /home/galymzhan/worker:8
Главное, что файл блокировки очищается правильно, поэтому вы можете снова запустить main.php
без проблем.
Теперь я проверяю, работает ли процесс ps
и деформирует скрипт php с помощью сценария bash
:
#!/bin/bash PIDS=`ps aux | grep onlytask.php | grep -v grep` if [ -z "$PIDS" ]; then echo "Starting onlytask.php ..." php /usr/local/src/onlytask.php >> /var/log/onlytask.log & else echo "onlytask.php already running." fi
и запускать скрипт bash
cron
каждую минуту.
<?php $sLock = '/tmp/yourScript.lock'; if( file_exist($sLock) ) { die( 'There is a lock file' ); } file_put_content( $sLock, 1 ); // A lot of code unlink( $sLock );
Вы можете добавить дополнительную проверку, написав pid, а затем проверите ее в файле file_exist-statement. Чтобы обеспечить его еще больше, вы можете получить все запущенные приложения с помощью «ps fax» end, проверьте, находится ли этот файл в списке.
попробуйте использовать наличие файла, а не флаг его флока:
$lockFile = "/tmp/"."onlyme.lock"; if (!file_exists($lockFile)) { touch($lockFile); echo "task started\n"; // // do something lengthy // unlink($lockFile); } else { echo "task already running\n"; }
Вы можете использовать файлы блокировки, как предполагали некоторые, но то, что вы действительно ищете, это функции PHP Semaphore . Они вроде как блокировки файлов, но разработаны специально для того, что вы делаете, ограничивая доступ к общим ресурсам.
Никогда не используйте unlink для файлов блокировки или других функций, таких как переименование. Это ломает ваш LOCK_EX на Linux. Например, после отмены или переименования файла блокировки любой другой скрипт всегда возвращает true из flock ().
Лучший способ обнаружить предыдущий действительный конец – записать в файл блокировки несколько байтов в конце блокировки, до LOCK_UN для обработки. И после того, как LOCK_EX прочитает несколько байтов из файлов блокировок и обработчика ftruncate.
Важное примечание: все проверено на PHP 5.4.17 на Linux и 5.4.22 на Windows 7.
Пример кода:
set Семафор:
$handle = fopen($lockFile, 'c+'); if (!is_resource($handle) || !flock($handle, LOCK_EX | LOCK_NB)) { if (is_resource($handle)) { fclose($handle); } $handle = false; echo SEMAPHORE_DENY; exit; } else { $data = fread($handle, 2); if ($data !== 'OK') { $timePreviousEnter = fileatime($lockFile); echo SEMAPHORE_ALLOW_AFTER_FAIL; } else { echo SEMAPHORE_ALLOW; } fseek($handle, 0); ftruncate($handle, 0); }
оставить семафор (лучший вызов в обработчике останова):
if (is_resource($handle)) { fwrite($handle, 'OK'); flock($handle, LOCK_UN); fclose($handle); $handle = false; }