phpfopenfcloseflock

Check if file is locked by concurrent process


I have a process that writes a file using file_put_contents():

file_put_contents ( $file, $data, LOCK_EX );

I have added the LOCK_EX parameter to prevent concurrent processes from writing to the same file, and prevent trying to read it when it's still being written to.

I'm having difficulties testing this properly due to the concurrent nature, and I'm not sure how to approach this. I've got this so far:

if (file_exists($file)) {
    $fp = fopen($file, 'r+');
    if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
        if ($wouldblock) {
            // how can I wait until the file is unlocked?
        } else {
            // what other reasons could there be for not being able to lock?
        }
    }
    // does calling fclose automatically close all locks even is a flock was not obtained above?
    fclose($file);
}

Questions being:

  1. Is there a way to wait until the file is not locked anymore, while keeping the option to give this a time limit?
  2. Does fclose() automatically unlock all locks when there would be another process that had locked the file?

Solution

  • I wrote a small test that uses sleep() so that I could simulate concurrent read/write processes with a simple AJAX call. It seems this answers both questions:

    1. when the file is locked, a sleep that approximates estimated write duration and subsequent lock check allow for waiting. This could even be put in a while loop with an interval.
    2. fclose() does indeed not remove the lock from the process that's already running as confirmed in some of the answers.

    PHP5.5 and lower on windows does not support the $wouldblock parameter according to the docs, I was able to test this on Windows + PHP5.3 and concluded that the file_is_locked() from my test still worked in this scenario: flock() would still return false just not have the $wouldblock parameter but it would still be caught in my else check.

    if (isset($_POST['action'])) {
        $file = 'file.txt';
        $fp = fopen($file, 'r+');
        if ($wouldblock = file_is_locked($fp)) {
            // wait and then try again;
            sleep(5);
            $wouldblock = file_is_locked($fp);
        }
        switch ($_POST['action']) {
            case 'write':
                if ($wouldblock) {
                    echo 'already writing';
                } else {
                    flock($fp, LOCK_EX);
                    fwrite($fp, 'yadayada');
                    sleep(5);
                    echo 'done writing';
                }
                break;
            case 'read':
                if ($wouldblock) {
                    echo 'cant read, already writing';
                } else {
                    echo fread($fp, filesize($file));
                }
                break;
        }
    
        fclose($fp);
        die();
    }
    
    function file_is_locked( $fp ) {
        if (!flock($fp, LOCK_EX|LOCK_NB, $wouldblock)) {
            if ($wouldblock) {
                return 'locked'; // file is locked
            } else {
                return 'no idea'; // can't lock for whatever reason (for example being locked in Windows + PHP5.3)
            }
        } else {
            return false;
        }
    }