How to create a PHP counter that may survive to concurrent visitors?

Viewed 91

I developed a very simple counter in PHP. It works as expected but occasionally it resets to zero. No idea why. I suspect it could be related to concurrent visitors but I have no idea how to prevent that in case I am correct. Here is the code:

function updateCounter($logfile) {
    $count = (int)file_get_contents($logfile);
    $file = fopen($logfile, 'w');
    if (flock($file, LOCK_EX)) {
        $count++ ;
        fwrite($file, $count);
        flock($file, LOCK_UN);
    }
    fclose($file);
    return number_format((float)$count, 0, ',', '.') ;
}

Thank you in advance.

3 Answers

file_get_contents on a locked file will probably get a "false" (== 0) and the logfile is probably unlocked again, when it comes to writing.

A classic race condition...

As file_get_contents() can return false accessing a previously locked file, the consequent fwrite() may write a zero or 1, resetting our counter to zero.

So we try to read the counter file after the locking has been succeeded for us.


function updateCounter($logfile) {
    //$count = (int)file_get_contents($logfile);
    if(file_exists($logfile)) {
      $mode = 'r+';     
    } else {
      $mode = 'w+';             
    }
    //
    $file = fopen($logfile, $mode);             
    //
    if (flock($file, LOCK_EX)) {
        //
        // read counter file:
        //
        $count = (int) fgets($file);
        $count++ ;
        //
        // point to the beginning of the file:
        //
        rewind($file);
        fwrite($file, $count);
        flock($file, LOCK_UN);
    }
    fclose($file);
    return number_format((float)$count, 0, ',', '.') ;
}
//
$logfile = "counter.log";
echo updateCounter($logfile);

Please see usernotes on https://www.php.net/manual/en/function.flock.php .

I would append a character into the file and use strlen on the file contents to get the hits. Please note that your file will get big overtime but this can be easily solved with a cronjob that sums it up and cache it into another readonly file.

You can also use !is_writeable and check if its locked and if so you can miss the hit or wait with a while loop until its writable. Tricky but it works. It depends how valuable each hit will be and how much effort you would like to invest in this counter.

Related