views:

1302

answers:

6

Part of my latest webapp needs to write to file a fair amount as part of its logging. One problem I've noticed is that if there are a few concurrent users, the writes can overwrite each other (instead of appending to file). I assume this is because of the destination file can be open in a number of places at the same time.

flock(...) is usually superb but it doesn't appear to work on NFS... Which is a huge problem for me as the production server uses a NFS array.

The closest thing I've seen to an actual solution involves trying to create a lock dir and waiting until it can be created. To say this lacks elegance is understatement of the year, possibly decade.

Any better ideas?

Edit: I should add that I don't have root on the server and doing the storage in another way isn't really feasible any time soon, not least within my deadline.

+1  A: 

Another dirty hack would be to flock() a "local" file, and only open / write to the NFS file if you hold the lock on the local file.

Edit: from the flock() page:

flock() will not work on NFS and many other networked file systems. Check your operating system documentation for more details.

Edit 2:

Of course there's always using the database to synchonise access (I'm assuming your app uses a db). This would be quite a performance hit if you're doing a lot of logging though.

If it's just for logging, do you actually need a centralised log file? Could you log locally (and even combine the logs when they rotate at the end of the day if needed)?

Greg
At the moment, that would be fine, but once this is out of development, it's going to be spanned across several virtual servers for load balancing and that would throw a[nother] spanner in the works.
Oli
+1  A: 

One approach could be to set up a Memcache instance, shared between each of your virtual servers. You could ape flock() by putting an entry of the filename in to cache when you start the local file operations, and wiping it when finished.

Each server can access this pool before a file operation, and see if this "lock" is present, e.g.

// Check for lock, using $filename as key
$lock = $memcache->get($filename);

if(!$lock) {
    // Set lock in memcache for $filename
    $memcache->set($filename, 1);

    // Do file operations...

    // Blow away "lock"
    $memcache->delete($filename);
}

Not the most elegant of solutions, but should enable you to control the locks from all servers in your setup with relative ease.

ConroyP
A: 

Should just use memcache add and avoid a race condition.

if ($memcache->add($filename, 1, 1))
{
   $memcache->delete($filename);
}
A: 

Even though you cannot flock() files on NFS and I/O can be asynchroneous, directory operations on NFS are atomic. That means that at at any given time, a directory does, or does not exist.

To implement your own NFS locking feature, check or create a directory when you want to have it locked and remove it when you're done.

Unfortunately, it's probably not compatible with any other application you didn't write yourself.

Wimmer
+4  A: 

Directory operations are NOT atomic under NFSv2 and NFSv3 (please refer to the book 'NFS Illustrated' by Brent Callaghan, ISBN 0-201-32570-5; Brent is a NFS-veteran at Sun).

NFSv2 has two atomic operations:

  • symlink
  • rename

With NFSv3 the create call is also atomic.

Knowing this, you can implement spin-locks for files and directories (in shell, not PHP):

lock current dir:

while ! ln -s . lock; do :; done

lock a file:

while ! ln -s ${f} ${f}.lock; do :; done

unlock (assumption, the running process really acquired the lock):

unlock current dir:

mv lock deleteme && rm deleteme

unlock a file:

mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme

Remove is also not atomic, therefore first the rename (which is atomic) and then the remove.

For the symlink and rename calls, both filenames have to reside on the same filesystem. My proposal: use only simple filenames and put file and lock into the same directory.

A: 

You can also use dio_fcntl() to lock files on NFS volumes. It requires the dio package, which may not be part of your php installation by default.

wonder