Как предотвратить запуск PHP скрипта более одного раза?

в настоящее время я пытался предотвратить onlytask.php скрипт от запуска более одного раза:

$fp = fopen("/tmp/"."onlyme.lock", "a+");
if (flock($fp, LOCK_EX | LOCK_NB)) {
  echo "task startedn";
  //
    while (true) {
      // do something lengthy
      sleep(10);
    }
  //
  flock($fp, LOCK_UN);
} else {
  echo "task already runningn";
}
fclose($fp);

и есть задание cron для выполнения вышеуказанного скрипта каждую минуту:

* * * * * php /usr/local/src/onlytask.php

это работает некоторое время. Через несколько дней, когда я это сделаю:

ps auxwww | grep onlytask

я обнаружил, что есть два экземпляров! Ни три, ни больше, ни один. Я убил одного из них. Через несколько дней появляются два экземпляров.

что не так в коде? Существуют ли другие альтернативы для ограничения только одного экземпляра onlytask.php работает?

п.С. мой /tmp/ папка не очищается. ls -al /tmp/*.lock показать, что файл блокировки был создан в первый день:

-rw-r--r--  1 root root    0 Dec  4 04:03 onlyme.lock

6 ответов


вы должны использовать 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 page:

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 check, если этот файл находится в списке.


попробуйте использовать наличие файла, а не его флаг flock:

$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 семафор функции. Они похожи на блокировки файлов, но разработаны специально для того, что вы делаете, ограничивая доступ к общим ресурсам.


никогда не используйте unlink для блокировки файлов или других функций, таких как переименование. Это сломать LOCK_EX на Linux. Например, после отсоединения или переименования файла блокировки любой другой скрипт всегда получает true от flock ().

лучший способ обнаружить предыдущую допустимую конечную запись для блокировки файла несколько байтов на конечной блокировке, прежде чем LOCK_UN обрабатывать. И после LOCK_EX считывает несколько байтов из файлов блокировки и ftruncate handle.

важное примечание: все протестировано на PHP 5.4.17 в Linux и 5.4.22 в Windows 7.

пример кода:

набор семафоров:

$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;
}