среда, 4 июля 2012 г.

Как создать постоянно работающий PHP скрипт

Иногда нужно сделать так, чтобы скрипт постоянно был активен, ожидая новых задач (в моём случае это рассылка писем).

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

Для того чтобы скрипт мог проверять сам себя (запущен ли процесс, не завис ли), я использую proc-файл - временный файл создаваемый скриптом. Мой proc-файл содержит только одно число - PID (ID процесса), записанное туда при первом запуске программы. Также из этого файла можно узнать время его модификации, что даёт возможность прервать запущенный процесс, если он выполняется слишком долго.

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

Класс для работы с proc-файлом

class ProcFile
{
 private static $file_name = 'php_job.proc';
 private static $file_ttl = 1200; // (sec) after what time the process considered "hanging"


 public static function setName($file_name_)
 {
  self::$file_name = $file_name_;
 }


 // check if process is already ON
 public static function isCurrent()
 {
  $proc_file = self::getPath();

  if (!file_exists($proc_file))
   return false;

  $pid = intval( file_get_contents($proc_file) );

  if (!self::checkIfRunning($pid))
   return false;

  // if process is hanging - it should be stoped
  if ((time() - filemtime($proc_file)) > self::$file_ttl)
  {
   self::killProcess($pid);
   return false;
  }

  return true;
 }


 // creates / overwrites proc-file
 public static function put()
 {
  file_put_contents(self::getPath(), getmypid());
 }


 // updates file modification time
 public static function update()
 {
  touch(self::getPath());
 }


 // proc-file full path
 private static function getPath()
 {
  return sys_get_temp_dir() . self::$file_name;
 }


 private static function checkIfRunning($pid)
 {
  $os_name = php_uname('s');
  $found  = false;

  switch ($os_name)
  {
   case 'Windows NT':

    $processes = explode("\n", shell_exec('tasklist.exe'));

    foreach ($processes as $process)
    {
     if (!strlen($process) OR !preg_match('/(.*?)(\d+).*$/', $process, $matches))
      continue;

     if ($matches[2] == $pid)
     {
      $found = true;
      break;
     }
    }
    break;

   case 'FreeBSD':

    $output = shell_exec('ps ax | grep \'^[[:space:]]*' . $pid . '\'');
    $found = (bool) strlen($output);
    break;

   default:

    trigger_error('Unknown OS "' . $os_name . '" in "' . __METHOD__ . '"');
  }

  return $found;
 }


 private static function killProcess($pid)
 {
  $os_name = php_uname('s');
  $killed  = false;

  switch ($os_name)
  {
   case 'Windows NT':

    shell_exec('taskkill /F /PID ' . $pid);
    $killed = true;
    break;

   case 'FreeBSD':

    shell_exec('kill ' . $pid);
    $killed = true;
    break;

   default:

    trigger_error('Unknown OS "' . $os_name . '" in "' . __METHOD__ . '"');
  }

  if ($killed)
   trigger_error('Previous sender instance is taking too long, killing...; ' . __FILE__ . ', ' . __LINE__, E_USER_NOTICE);
 }
}

Принцип работы

// Указать имя файла
ProcFile::setName('mail_send.proc');

// Проверить текущее состояния proc-файла, и действовать соотвественно

if (ProcFile::isCurrent())
{
 echo 'let the previous instance to work' . NL;
 exit;

} else {

 echo 'start anew' . NL;
 ProcFile::put();
}

while (true)
{
 $res = db_query(...);

 while ($row = db_fetch($res))
 {
  // работа скрипта

  // обновить время модификации proc-файла
  ProcFile::update();
 }

 // обновить время модификации proc-файла
 ProcFile::update();

Как запускать скрипт в Windows


Можно создать ярлык вида:

"C:\Program Files\PHP\php.exe" -f с:\htdocs\newsletters\send.php

и поместить его в папку Control Panel -> Scheduled Tasks. Затем два раза кликнув по ярлыку, можно настроить время запуска. Но проблема в том, что при запуске скрипта будет появляться окно cmd.exe, и я не нашёл способа от него избавиться.

Есть ещё вариант запускать скрипт как Windows-сервис, но это отдельная тема.

В моём случае скрипты на Windows-сервере запускаются с помощью сторонней программы подобия cron.

Как запускать скрипт в FreeBSD / Linux


Тут процесс отработанный. В crontab записывается строка вида:

*/1 * * * * user php -f /usr/script/send.php

, и скрипт запускается каждую минуту. За более полной информацией можно обратиться в документацию - http://crontab.org/.

Комментариев нет:

Отправить комментарий