I'm trying to understand non blocking flock and the wouldblock argument
$fp = fopen('/tmp/lock.txt', 'r+');
if(flock($fp, LOCK_EX | LOCK_NB, $wouldblock)) {
echo 'Lock obtained';
}
else{
echo 'Unable to obtain lock';
}
fclose($fp);
Documentation says about wouldblock:
The optional third argument is set to 1 if the lock would block (EWOULDBLOCK errno condition).
Reproducing in a test enviroment the concurrent condition, if another process has gained lock, the flock function will immeditatly return FALSE (non blocking)
So why should I care about $wouldblock value if the return value of flock function in non-blocking mode allready tells me that the lock couldn't be obtained?
I can't get the difference between flock function returning FALSE and the $wouldblock argument setted to 1, and what the $wouldblock argument is usefull for.
This is because flock()
may fail not only because lock is already gained somewhere else.
In such case it wouldn't block waiting for lock being released, but it would immediately return false. In other words with LOCK_NB
if flock returns false and wouldblock=1 then it means, it tried to gain lock but it is already acquired somewhere else. But if flock with LOCK_NB
returns false and wouldblock=0 then it means something really bad happen and flock didn't even consider waiting to gain lock as this was completely impossible.
Check out this code(here is also a gist):
<?php
// Let's create /tmp/ninja-lock1.txt ...
$fp0 = fopen('/tmp/ninja-lock1.txt', 'c');
// ... and close it imiedietly
fclose($fp0);
// File handler $fp0 was closed so flock()
// is unable to use it to gain lock.
// It will fail with wouldblock set to 0
// as it doesn't make sense to wait on unusable file handle.
//
// BTW flock() throws in such case warning "x is not a valid stream resource".
// Just for the purpose of clear output from this example
// I've suppressed it with @ - don't use @ in production
$flockResult = @flock($fp0, LOCK_EX | LOCK_NB, $wouldblock);
printf("flock=%b; wouldblock=%d (Acquire lock: %s)\n", $flockResult, $wouldblock, "failure, broken file handle");
// Two handlers for /tmp/ninja-lock2.txt
// to show flock() blocking result.
$fp1 = fopen('/tmp/ninja-lock2.txt', 'c');
$fp2 = fopen('/tmp/ninja-lock2.txt', 'c');
// Nobody is locking on /tmp/ninja-lock2.txt,
// so it will acquire lock and wouldblock will be 0
$flockResult = flock($fp1, LOCK_EX | LOCK_NB, $wouldblock);
printf("flock=%b; wouldblock=%d (Acquire lock: %s)\n", $flockResult, $wouldblock, "success");
// File is locked on $fp1 handle so flock won't acquire lock
// and wouldblock will be 1
$flockResult = flock($fp2, LOCK_EX | LOCK_NB, $wouldblock);
printf("flock=%b; wouldblock=%d (Acquire lock: %s)\n", $flockResult, $wouldblock, "failure, already acquired somewhere else");
// Result:
// $ php flock.php
// flock=0; wouldblock=0 (Acquire lock: failure, broken file handle)
// flock=1; wouldblock=0 (Acquire lock: success)
// flock=0; wouldblock=1 (Acquire lock: failure, already acquired somewhere else)
?>
Also just to clear any confusion of future readers it's worth to note that checking EWOULDBLOCK
makes only sense for flock() with LOCK_NB
flag, as in blocking mode it's either success and block or failure and no block.
You can confirm this by looking into php source code for flock:
PHPAPI int php_flock(int fd, int operation)
#if HAVE_STRUCT_FLOCK /* {{{ */
{
struct flock flck;
int ret;
flck.l_start = flck.l_len = 0;
flck.l_whence = SEEK_SET;
if (operation & LOCK_SH)
flck.l_type = F_RDLCK;
else if (operation & LOCK_EX)
flck.l_type = F_WRLCK;
else if (operation & LOCK_UN)
flck.l_type = F_UNLCK;
else {
errno = EINVAL;
return -1;
}
ret = fcntl(fd, operation & LOCK_NB ? F_SETLK : F_SETLKW, &flck);
if (operation & LOCK_NB && ret == -1 &&
(errno == EACCES || errno == EAGAIN))
errno = EWOULDBLOCK;
if (ret != -1) ret = 0;
return ret;
}
EWOULDBLOCK
is set only if operation & LOCK_NB && ret == -1 &&
(errno == EACCES || errno == EAGAIN)
.
If you are more interested in implementation you can also read man page of fcntl, mostly parts about F_SETLK
and F_SETLKW
:
F_SETLK
Acquire a lock (when l_type is F_RDLCK or F_WRLCK) or release a lock (when l_type is F_UNLCK) on the bytes specified by the l_whence, l_start, and l_len fields of lock. If a conflicting lock is held by another process, this call returns -1 and sets errno to EACCES or EAGAIN.
F_SETLKW
As for F_SETLK, but if a conflicting lock is held on the file, then wait for that lock to be released. If a signal is caught while waiting, then the call is interrupted and (after the signal handler has returned) returns immediately (with return value -1 and errno set to EINTR).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With