asDca21
asDca21

Reputation: 161

file_exists returns false between file_put_content (or fwrite) and unlink in second execution

I'm working on a cron system and need to execute a script only once at a time. By using the following codes, I execute the script first time and while it's looping (for delaying purpose), executing it again but file_exists always returns false while first execution returns content of file after loop is done.

Cronjob.php:

include "Locker.class.php";

Locker::$LockName = __DIR__.'/OneTime_[cron].lock';
$Locker = new Locker();

for ($i = 0 ; $i < 1000000; $i++){
    echo 'Z';
    $z = true;
    ob_end_flush();
    ob_start();
}

Locker.class.php:

class Locker{
    static $LockName;
    function __construct($Expire){

        if (!basename(static::$LockName)){
            die('Locker: Not a filename.');
        }

        // It doesn't help
        clearstatcache();


        if (file_exists(static::$LockName)){    // returns false always
            die('Already running');
        } else {
            $myfile = fopen(static::$LockName, "x");    // Tried with 'x' and 'w', no luck
            fwrite($myfile, 'Keep it alive');           // Tried with file_put_content also, no luck
            fclose($myfile);
        }

        // The following function returns true by the way!
        // echo file_exists(static::$LockName);

    }
    function __destruct(){
        // It outputs content
        echo file_get_contents(static::$LockName);
        unlink(static::$LockName);
    }
}

What is the problem? Why file_exists returns false always?

Upvotes: 1

Views: 188

Answers (2)

drew010
drew010

Reputation: 69967

If you're goal is to prevent a potentially long running job from executing multiple copies at the same time, you can take a simpler approach and just flock() the file itself.

This would go in cronjob.php

<?php

$wb = false;
$fp = fopen(__FILE__, 'r');
if (!$fp) die("Could not open file");

$locked = flock($fp, LOCK_EX | LOCK_NB, $wb);

if (!$locked) die("Couldn't acquire lock!\n");

// do work here
sleep(20);

flock($fp, LOCK_UN);
fclose($fp);

To address your actual question, I found that by running your code, the reason the file is going away is because on subsequent calls, it outputs Already running if a job is running, and then the second script invokes the destructor and deletes the file before the initial task finishes running.

The flock method above solves this problem. Otherwise, you'll need to ensure that only the process that actually creates the lock file is able to delete it (and take care that it never gets left around too long).

Upvotes: 1

Barmar
Barmar

Reputation: 781716

I suspect the PHP parser has noticed that you never use the variable $Locker, so it immediately destroys the object, which runs the destructor and removes the file. Try putting a reference to the object after the loop:

include "Locker.class.php";

Locker::$LockName = __DIR__.'/OneTime_[cron].lock';
$Locker = new Locker();

for ($i = 0 ; $i < 1000000; $i++){
    echo 'Z';
    $z = true;
    ob_end_flush();
    ob_start();
}

var_dump($Locker);

Upvotes: 1

Related Questions