PHP - Почему flock () не работает надежно с добавляемым файлом? - PullRequest
0 голосов
/ 01 мая 2020

Если я одновременно запускаю следующий скрипт в трех отдельных терминалах, я получаю файл CSV, в котором отсутствует от 25 до 50 строк (общее количество, как ожидается, будет 3001 строк после завершения всех сценариев).

<?php
  // Run this command in three separate consoles, concurrently.
  //   - Run the first with php -f ./test.php 1
  //   - Run the second with php -f ./test.php 2
  //   - Run the second with php -f ./test.php 3

  $prefix = $argv[1];

  for ($value = 0; $value < 1000; ++$value) {
    $sleep_amount = rand(50000, 500000);

    print "Sleeping for {$sleep_amount} micro-seconds...";
    usleep($sleep_amount);

    print "{$value}\n";

    lock_and_write($prefix, $value);
  }

  function lock_and_write($prefix, $value) {
    $file_path = "test.csv";
    $file_handle = fopen($file_path, 'c');

    if ($file_handle === FALSE) {
      throw new \RuntimeException(
        sprintf('Could not open CSV file for writing [%s].', $file_path)
      );
    }

    try {
      flock($file_handle, LOCK_EX);
      fseek($file_handle, 0, SEEK_END);

      $write_header = (ftell($file_handle) === 0);

      if ($write_header) {
        fputcsv($file_handle, array('prefix', 'value'));
      }

      fputcsv($file_handle, array($prefix, $value));
    }    
    finally {
      fflush($file_handle);
      flock($file_handle, LOCK_UN);
      fclose($file_handle);
    }
  }

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

<?php
  // Run this command in three separate consoles, concurrently.
  //   - Run the first with php -f ./test.php 1
  //   - Run the second with php -f ./test.php 2
  //   - Run the second with php -f ./test.php 3
  $prefix = $argv[1];

  for ($value = 0; $value < 1000; ++$value) {
    $sleep_amount = rand(25000, 500000);

    print "Sleeping for {$sleep_amount} micro-seconds...";
    usleep($sleep_amount);

    print "{$value}\n";

    lock_and_write($prefix, $value);
  }

  function lock_and_write($prefix, $value) {
    $file_path = "test.csv";
    $lock_file = "{$file_path}.lockfile";

    $lock_handle = fopen($lock_file, 'w');

    if ($lock_handle === FALSE) {
      throw new \InvalidArgumentException(
        sprintf('Could not open CSV lock file [%s].', $lock_file)
      );
    }

    if (flock($lock_handle, LOCK_EX)) {
      $file_handle = fopen($file_path, 'c');

      if ($file_handle === FALSE) {
        throw new \RuntimeException(
          sprintf('Could not open CSV file for writing [%s].', $file_path)
        );
      }

      try {
        if (fseek($file_handle, 0, SEEK_END) !== 0) {
          throw new \RuntimeException(
            sprintf('Could not open CSV file for writing [%s].', $file_path)
          );
        }

        $write_header = (ftell($file_handle) === 0);

        if ($write_header) {
          fputcsv($file_handle, array('prefix', 'value'));
        }

        fputcsv($file_handle, array($prefix, $value));
      }
      finally {
        fflush($file_handle);
        fclose($file_handle);

        flock($lock_handle, LOCK_UN);
        fclose($lock_handle);
      }
    }
    else {
      echo "Failed to acquire lock!";
    }
  }

Почему требуется отдельный файл блокировки? Является ли блокировка добавляемого файла ненадежной? Есть ли задокументированная известная проблема между flock() и fputcsv()?

В случае, если вам интересно, почему я не использую режим добавления (a), у меня была одна теория о том, почему это не удалось, потому что Я подумал, что, возможно, fopen() ищет в конце файла с момента открытия файла , а не ищет в тот момент, когда запись происходит после получения блокировки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...