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).