Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between return value of non-blocking flock function and the $wouldblock argument?

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.

like image 901
nulll Avatar asked Jun 26 '14 07:06

nulll


1 Answers

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

like image 74
Kamil Dziedzic Avatar answered Nov 19 '22 16:11

Kamil Dziedzic