12

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,332
  • 11
  • 53
  • 99

3 Answers3

17

You should put a lock on the file:

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

fclose($fp);
8ctopus
  • 2,617
  • 2
  • 18
  • 25
Rho
  • 1,010
  • 2
  • 11
  • 17
  • 3
    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
  • 1
    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
  • 1
    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
  • To clarify the argument `LOCK_EX` will pause the script until the lock can be obtained. To avoid this behavior and fail instead use `LOCK_EX | LOCK_NB` – 8ctopus Jun 17 '22 at 06:03
8

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")) {
    # optional wait between lock attempts, could use usleep()
    sleep(1);
    # may want to return early if reach a max number of tries to get lock
}
# use the file
fopen("file"...)
# release lock
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.
Walf
  • 8,535
  • 2
  • 44
  • 59
magallanes
  • 6,583
  • 4
  • 54
  • 55
4

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
  • 41
  • 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