Проверьте, заблокирован ли файл параллельным процессом - PullRequest
1 голос
/ 12 марта 2019

У меня есть процесс, который записывает файл, используя file_put_contents():

file_put_contents ( $file, $data, LOCK_EX );

Я добавил параметр LOCK_EX, чтобы предотвратить одновременную запись процессов в один и тот же файл, и не пытайтесь прочитать его, когда он все еще записывается.

У меня возникают трудности при правильном тестировании из-за одновременной природы, и я не уверен, как к этому подойти.У меня есть это до сих пор:

if (file_exists($file)) {
    $fp = fopen($file, 'r+');
    if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
        if ($wouldblock) {
            // how can I wait until the file is unlocked?
        } else {
            // what other reasons could there be for not being able to lock?
        }
    }
    // does calling fclose automatically close all locks even is a flock was not obtained above?
    fclose($file);
}

Вопросы:

  1. Есть ли способ подождать, пока файл больше не заблокирован, сохраняя при этом возможность дать этоограничение по времени?
  2. Разве fclose() автоматически разблокирует все блокировки, когда будет другой процесс, который заблокировал файл?

Ответы [ 3 ]

1 голос
/ 12 марта 2019

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

lock_file.php

<?php
  /*
  Reference Material
  http://en.wikipedia.org/wiki/ACID
  */
  class Exclusive_Lock {
    /* Private variables */
    public $filename; // The file to be locked
    public $timeout = 30; // The timeout value of the lock
    public $permission = 0755; // The permission value of the locked file
    /* Constructor */
    public function __construct($filename, $timeout = 1, $permission = null, $override = false) {
      // Append '.lck' extension to filename for the locking mechanism
      $this->filename = $filename . '.lck';
      // Timeout should be some factor greater than the maximum script execution time
      $temp = @get_cfg_var('max_execution_time');
      if ($temp === false || $override === true) {
        if ($timeout >= 1) $this->timeout = $timeout;
        set_time_limit($this->timeout);
      } else {
        if ($timeout < 1) $this->timeout = $temp;
        else $this->timeout = $timeout * $temp;
      }
      // Should some other permission value be necessary
      if (isset($permission)) $this->permission = $permission;
    }
    /* Methods */
    public function acquireLock() {
      // Create the locked file, the 'x' parameter is used to detect a preexisting lock
      $fp = @fopen($this->filename, 'x');
      // If an error occurs fail lock
      if ($fp === false) return false;
      // If the permission set is unsuccessful fail lock
      if (!@chmod($this->filename, $this->permission)) return false;
      // If unable to write the timeout value fail lock
      if (false === @fwrite($fp, time() + intval($this->timeout))) return false;
      // If lock is successfully closed validate lock
      return fclose($fp);
    }
    public function releaseLock() {
      // Delete the file with the extension '.lck'
      return @unlink($this->filename);
    }
    public function timeLock() {
      // Retrieve the contents of the lock file
      $timeout = @file_get_contents($this->filename);
      // If no contents retrieved return error
      if ($timeout === false) return false;
      // Return the timeout value
      return intval($timeout);
    }
  }
?>

Простое использование следующим образом:

  include("lock_file.php");
  $file = new Exclusive_Lock("my_file.dat", 2);
  if ($file->acquireLock()) {
    $data = fopen("my_file.dat", "w+");
    $read = "READ: YES";
    fwrite($data, $read);
    fclose($data);
    $file->releaseLock();
    chmod("my_file.dat", 0755);
    unset($data);
    unset($read);
  }

Если вы хотите добавить более сложный уровень, вы можете использовать другой прием ... используйте while (1), чтобы инициализировать бесконечный цикл, который прерывается только при получении эксклюзивной блокировки, не рекомендуется, поскольку может блокировать ваш сервер на неопределенное время. .

  include("lock_file.php");
  $file = new Exclusive_Lock("my_file.dat", 2);
  while (1) {
    if ($file->acquireLock()) {
      $data = fopen("my_file.dat", "w+");
      $read = "READ: YES";
      fwrite($data, $read);
      fclose($data);
      $file->releaseLock();
      chmod("my_file.dat", 0755);
      unset($data);
      unset($read);
      break;
    }
  }

file_put_contents() очень быстрый и записывает непосредственно в файл, но, как вы говорите, имеет ограничение ... условие гонки существует и может произойти, даже если вы попытаетесь использовать LOCK_EX. Я думаю, что класс php более гибкий и удобный ...

Увидимся в этой теме, которая рассматривает похожий вопрос: php flock, когда файл заблокирован одним процессом

0 голосов
/ 13 марта 2019

Я написал небольшой тест, использующий sleep(), чтобы можно было имитировать параллельные процессы чтения / записи с помощью простого вызова AJAX.Похоже, что это отвечает на оба вопроса:

  1. , когда файл заблокирован, режим ожидания, который приблизительно соответствует предполагаемой продолжительности записи, и последующая проверка блокировки позволяют ожидать.Это можно даже поместить в цикл while с интервалом.
  2. fclose() действительно не снимает блокировку с процесса, который уже запущен, что подтверждается в некоторых ответах.

PHP5.5 и ниже в Windows не поддерживает параметр $wouldblock в соответствии с документами, я смог проверить это на Windows + PHP5.3 и пришел к выводу, что file_is_locked() из моего теста все ещеработал в этом сценарии: flock() все равно будет возвращать false, просто не имея параметра $wouldblock, но он все равно будет пойман в моей проверке else.

if (isset($_POST['action'])) {
    $file = 'file.txt';
    $fp = fopen($file, 'r+');
    if ($wouldblock = file_is_locked($fp)) {
        // wait and then try again;
        sleep(5);
        $wouldblock = file_is_locked($fp);
    }
    switch ($_POST['action']) {
        case 'write':
            if ($wouldblock) {
                echo 'already writing';
            } else {
                flock($fp, LOCK_EX);
                fwrite($fp, 'yadayada');
                sleep(5);
                echo 'done writing';
            }
            break;
        case 'read':
            if ($wouldblock) {
                echo 'cant read, already writing';
            } else {
                echo fread($fp, filesize($file));
            }
            break;
    }

    fclose($fp);
    die();
}

function file_is_locked( $fp ) {
    if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
        if ($wouldblock) {
            return 'locked'; // file is locked
        } else {
            return 'no idea'; // can't lock for whatever reason (for example being locked in Windows + PHP5.3)
        }
    } else {
        return false;
    }
}
0 голосов
/ 12 марта 2019

Ответ на первый вопрос здесь Как определить завершение с помощью file_put_contents () в php? и потому что PHP является однопоточным, единственным решением является использование расширения ядра PHP с помощью PTHREADS и один хороший простой статья об этом https://www.mullie.eu/parallel-processing-multi-tasking-php/

Здесь ответ на второй вопрос Будет ли разблокированный файл разблокирован, когда процесс неожиданно умирает?

Функция fclose () разблокирует только действительный дескриптор, открытый с помощью fopen () или fsockopen (), поэтому, если дескриптор все еще действителен, да, он закроет файл и снимет блокировку.

...