humanityANDpeace
humanityANDpeace

Reputation: 4637

Does php's file_get_contents ignore file locking?

I have read php's manual page on the 'file_get_contents' function which does not state anything about how 'file_get_contents` behaves with respect to php's file locking. However in the comment section user Chris suggests that

file_get_contents does not normally respect PHP's flock locking, i.e. advisory locking.

You can workaround this with some extra code to request a shared lock, like...

<?php
$tmp = fopen($path, 'rb');
@flock($tmp, LOCK_SH);
$contents = file_get_contents($path);
@flock($tmp, LOCK_UN);
fclose($tmp);
?>

which I have tested with success. I have also tested that even though a file has been locked with flock() exclusively LOCK_EX it was possible to have another php process read the file via file_get_contents as the comment would have suggested.

However, and that is mainly why I ask for information, I have read a webpage titled "Reading locked files in PHP", which claimed the following with regards to file_get_contents and file locking.

Reading a locked file with file_get_contents()

This is one the worst way to read a file while it is locked and modified, because:
- file_get_contents() will return an empty string (like in "")
- filesize() will return the actual number of bytes written to the file

I this claim correct? I run some tests, locking a file exclusively and constantly writing to it, while using file_get_contents in another php process to read the file and have not experienced the behaviour that as stated above

file_get_contents() will return an empty string (like in "")

Is it true in general that, php's file_get_contents does not care anything about the advisory file locking. Also, am I assuming correctly that the claims made in the webpage of the empty string returned by file_get_contents is empty "", is only true if the file is empty, or temporarily empty (while being modified) but not generally empty (only for the reason of the file being flock()ed)?

Upvotes: 9

Views: 4903

Answers (3)

Rocker2102
Rocker2102

Reputation: 3

I encountered a weird case when working on a file, which might get updated from other scripts (basically the same script but spawned as a separate process).

In my case, I was able to read an exclusively locked file (LOCK_EX) using file_get_contents, but, it issued a notice

Notice SS

PHP Notice: file_get_contents(): Read of 8192 bytes failed with errno=13 Permission denied in E:\Xampp\htdocs\api\test.php on line 18

It still read the file fine, the data was correct & not corrupted. It was easily suppressable by using @ before the function call but it seemed hacky to me.

So after lots of searching & testing, I settled with stream_get_contents using the same file handle which I used earlier to acquire the lock.

I still don't know why file_get_contents was behaving that way but ig it was because this function call was trying to open another handle & encountered the lock?

Code:

function update_history($task_id, $details)
{
  $history_file = "history.json";

  $file = @fopen($history_file, "c+");
  try {
    if (!$file) {
      throw new Exception("Error opening history file: " . $history_file); // Log the error
    }

    if (!flock($file, LOCK_EX)) {
      fclose($file);
      throw new Exception("Error acquiring lock on history file");
    }

    $history = file_get_contents($history_file);
    $history = $history ? json_decode($history, true) : [];

    $history[] = [
      "task_id" => $task_id,
      "details" => $details,
      "completed_at" => time() * 1000
    ];

    $json_data = json_encode($history, JSON_PRETTY_PRINT);
    if ($json_data === false) {
      throw new Exception("Error encoding history data to JSON: " . json_last_error_msg());
    }

    ftruncate($file, 0);
    rewind($file);
    fwrite($file, $json_data);
    fflush($file);
  } catch (Exception $e) {
    if ($file && is_resource($file)) {
      flock($file, LOCK_UN);
      fclose($file);
    }

    throw $e;
  }

  if ($file && is_resource($file)) {
    flock($file, LOCK_UN);
    fclose($file);
  }
}

It showed the notice in both cases - if the file already existed or when it got created later.

Upvotes: 0

kb1
kb1

Reputation: 1

flock in shared mode will allow read access to the file. flock in exclusive mode will allow read/write access. A shared or exclusive lock will cause the next thread to set an exclusive lock to wait until the lock is released. While waiting a following file_get_contents will not be excuted until the lock is released by the thread that set the lock. Multiple threads may set a shared lock and continue with read access. The file_get_contents will be executed without wait (unless there is an exclusive lock active.)

Consider a situation where you want to protect a section of code that only one thread at a time may execute. You set an exclusive lock on any file, that may never be written to and only exists as a lock sentinal. After the section of code you unlock and allow the next thread to continue. Beware, the protected section must be short to prevent delays and queue's. Also if you use multiple locks they shoud be used in a common order to prevent deadlocks.

Upvotes: 0

ChristianM
ChristianM

Reputation: 1823

flock is relatively independent of the file operations, you can even use fopen on a locked file. You as developer are responsible for checking/using flock everywhere you require a lock.

But yes in that regard it's true that file_get_contents has no build-in way to acquire a read lock when reading the file. So the workaround would be the way to go.

file_put_contents allows you to get a lock for writing though.

Upvotes: 6

Related Questions