9

I had a newcomer (the next door teenager) write some php code to track some usage on my web site. I'm not familiar with php so I'm asking a bit about concurrent file access.

My native app (on Windows), occasionally logs some data to my site by hitting the URL that contains my php script. The native app does not examine the returned data.

        $fh = fopen($updateFile, 'a') or die("can't open file");
        fwrite($fh, $ip);
        fwrite($fh, ', ');
        fwrite($fh, $date);
        fwrite($fh, ', ');
        fwrite($fh, implode(', ', $_GET));
        fwrite($fh, "\r\n");
        fclose($fh);

This is a low traffic site, and the data is not critical. But what happens if two users collide and two instances of the script each try to add a line to the file? Is there any implicit file locking in php?

Is the code above at least safe from locking up and never returning control to my user? Can the file get corrupted? If I have the script above delete the file every month, what happens if another instance of the script is in the middle of writing to the file?

RobertFrank
  • 7,252
  • 11
  • 50
  • 96

3 Answers3

15

You should put a lock on the file:

<?php
$fp = fopen($updateFile, 'w+');
if(flock($fp, LOCK_EX)) {
fwrite($fp, 'a');
flock($fp, LOCK_UN);
} else {
echo 'can\'t lock';
}

fclose($fp);
Kristoffer la Cour
  • 2,551
  • 3
  • 24
  • 36
Rho
  • 982
  • 2
  • 10
  • 17
  • 2
    Exactly what I was looking for, Raymond. Thanks. From other reading I've just done (http://www.tuxradar.com/practicalphp/8/11/0), it appears that the echo-statement might never be reached. Won't a second process block on the flock call until the file is unlocked by a first process? Doesn't the flock need an OR LOCK_NB so that the echo statement can be reached? – RobertFrank Mar 27 '11 at 14:36
  • I had hoped to get a response from Mr Ho or someone else here as to whether my observation that the echo-statement above would be reached or not. – RobertFrank Mar 30 '11 at 12:00
  • 4
    Yes, you need a `|LOCK_NB` in the code, otherwise the lock would lock if it was locked already :) – cweiske May 07 '11 at 10:37
  • Bear in mind that `flock()` is mandatory on Windows (it will always lock a file) but advisory on Linux (it may not actually perform a lock) so will need some testing on any non-Windows platform. – Jason Oct 27 '15 at 12:32
  • To clarify @Jason's point: the meaning of `advisory` is that you need to make sure that **all** code that accesses the file calls `flock`. The file isn't actually "locked" at OS level. For instance, suppose you have code elsewhere that is accessing the file (maybe in a language other than php) - your php will believe it has a lock, but it won't really... – ToolmakerSteve Jun 18 '19 at 22:54
7

For the record, I worked in a library that does that:

https://github.com/EFTEC/DocumentStoreOne

It allows to CRUD documents by locking the file. I tried 100 concurrent users (100 calls to the PHP script at the same time) and it works.

However, it doesn't use flock but mkdir:

while (!@mkdir("file.lock")) {
   // use the file
   fopen("file"...)
   @rmdir("file.lock")
}

Why?

  1. mkdir is atomic, so the lock is atomic: In a single step, you lock or you don't.
  2. It's faster than flock(). Apparently flock requires several calls to the file system.
  3. flock() depends on the system.
  4. I did a stress test and it worked.
hyde
  • 55,609
  • 19
  • 114
  • 170
magallanes
  • 6,141
  • 4
  • 51
  • 52
3

Since this is an append to the file, the best way would be to aggregate the data and write it to the file in one fwrite(), providing the data to be written is not bigger then the file buffer. Ofcourse you don't always know the size of the buffer, so flock(); is always a good option.

user740145
  • 31
  • 2
  • 1
    I would do both: keep the write to a single `fwrite()` *and* lock the file. On some Linux platforms and some older filesystems, `flock()` may not actually lock the file. – Jason Oct 27 '15 at 12:34