When working with PHP applications on an NFS (Network File System) mounted filesystem, you'll quickly discover that the standard flock()
function often fails to work as expected. This occurs because traditional file locking mechanisms don't always propagate reliably across network filesystems.
NFS implements locking differently than local filesystems due to its distributed nature:
- NFS v2/v3 requires a separate lock manager (rpc.lockd)
- NFS v4 includes native locking but implementation varies
- Network latency can cause timeout issues
- Client caching may lead to stale locks
Here are several approaches that work effectively on NFS:
1. Using mkdir as Atomic Lock
Create locks using directory operations which are atomic in NFS:
function nfs_lock($lockfile) {
$lockdir = $lockfile . '.lock';
while (!@mkdir($lockdir)) {
usleep(100000); // Sleep for 100ms
}
return true;
}
function nfs_unlock($lockfile) {
$lockdir = $lockfile . '.lock';
return @rmdir($lockdir);
}
2. Advisory Locking with lockf
For systems where lockf
is available:
$fp = fopen('/path/to/file', 'r+');
if (lockf($fp, LOCK_EX)) {
// Critical section
lockf($fp, LOCK_UN);
}
fclose($fp);
3. Database-based Locking
When working with NFS-mounted files, consider moving locks to a database:
// Using MySQL for locking
function db_lock($name, $timeout = 30) {
$result = db_query(
"GET_LOCK('".db_escape($name)."', $timeout)"
);
return $result == 1;
}
function db_unlock($name) {
db_query("RELEASE_LOCK('".db_escape($name)."')");
}
When implementing NFS file locks:
- Always set appropriate timeout values
- Implement proper cleanup in case of crashes
- Consider using a combination of approaches
- Profile performance impact on your specific NFS setup
Create a test script to verify lock behavior:
$lockfile = '/nfs/mount/test.lock';
if (nfs_lock($lockfile)) {
echo "Lock acquired, working for 5 seconds...\n";
sleep(5);
nfs_unlock($lockfile);
echo "Lock released\n";
} else {
echo "Failed to acquire lock\n";
}
When working with PHP applications on NFS-mounted directories, many developers encounter unexpected behavior with the flock()
function. The issue stems from fundamental differences in how local filesystems and NFS handle file locking.
PHP's flock()
relies on advisory locking at the kernel level, but NFS implementations vary in their support for file locks. The key problems include:
- Non-mandatory locking nature (advisory only)
- Potential for locks to disappear after server reboots
- Network latency affecting lock acquisition timing
- Inconsistent implementation across NFS versions
Here are three approaches that have proven effective in production environments:
1. Using Lock Files with Unique Naming
Create uniquely named lock files as atomic operation indicators:
function nfsSafeLock($filePath) {
$lockFile = $filePath . '.' . getmypid() . '.' . microtime(true) . '.lock';
if (!@symlink($filePath, $lockFile)) {
return false;
}
return $lockFile;
}
function nfsSafeUnlock($lockFile) {
unlink($lockFile);
}
2. Database-Based Locking
For systems already using a database, leverage transactional locks:
// MySQL example
function acquireDbLock($name, $timeout = 30) {
$conn = new PDO(...);
$conn->exec("SELECT GET_LOCK('$name', $timeout)");
return $conn;
}
function releaseDbLock($conn, $name) {
$conn->exec("SELECT RELEASE_LOCK('$name')");
$conn = null;
}
3. Using NFSv4 Lease-Based Locking
If your NFS server supports v4+, you can use its native locking mechanism:
$fp = fopen($file, 'r+');
if (flock($fp, LOCK_EX | LOCK_NB, $wouldblock)) {
if ($wouldblock) {
// Lock would block (contended)
} else {
// Exclusive lock obtained
}
}
When implementing NFS file locking:
- Always set reasonable timeout values
- Implement proper lock cleanup in exception handlers
- Monitor for stale locks in your application
- Consider using distributed lock managers for critical systems
For high-availability systems, consider:
- Redis-based distributed locks
- ZooKeeper coordination services
- Consul's lock mechanism