Search code examples
phpphp-7.4

The require method return int when it is not possible in PHP


I have following code, it saves some php code into a file, then load it, run again, sometimes the require method returns int, why does this happen?

demo.php

<?php

$f = function()  use($a){
    $cachePath = '/tmp/t.php';

    $code = '<?php';
    $code .= "\n\n";
    $code .= 'return ' . var_export([], true) . ';';

    file_put_contents($cachePath, $code, LOCK_EX);

    if (file_exists($cachePath)) {
        // Sometime the following line returns int,why?
        $result = require($cachePath);
        if (!is_array($result)) {
            var_dump($result, $cachePath, file_get_contents($cachePath));
            exit("ok");
        }
        var_dump($result);
    }
};


for($i=0;$i<1000000;$i++) {
    $f();
}

How to reproduce?

Run above code with two php processes by

php demo.php

Solution

  • This problem isn't reproducible if there's only one executor of the script at all times.

    If you're talking about running this script in parallel, well, the problem is that writing the file in exclusive mode isn't going to protect you from reading the file half-way through writing later on.

    A process could be writing the file (and having the lock) but require doesn't adhere to that lock (file system locks are advisory, not enforced).

    So the correct solution would be:

    <?php
    
    $f = function()  use($a){
        $cachePath = '/tmp/t.php';
    
        /* Open the file for writing only. If the file does not exist, it is created.
           If it exists, it is neither truncated (as opposed to 'w'), nor the call to this function fails (as is the case with 'x').
           The file pointer is positioned on the beginning of the file. 
           This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file, as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can be used after the lock is requested). */
        $fp = fopen($cachePath, "c");
    
        $code = '<?php';
        $code .= "\n\n";
        $code .= 'return ' . var_export([], true) . ';';
     
        // acquire exclusive lock, waits until lock is acquired
        flock($fp, LOCK_EX);
        // clear the file
        ftruncate($fp, 0);
        // write the contents
        fwrite($fp, $code);
    
        //wrong (see my answer as to why)
        //file_put_contents($cachePath, $code, LOCK_EX);
    
        //not needed
        //if (file_exists($cachePath)) {
            // Lock is held during require
            $result = require($cachePath);
            if (!is_array($result)) {
                var_dump($result, $cachePath, file_get_contents($cachePath));
                exit("ok");
            }
            var_dump($result);
        //}
        // closing the file implicitly releases the lock
        fclose($fp);
    };
    
    
    for($i=0;$i<1000000;$i++) {
        $f();
    }
    

    Note that the lock isn't released and re-acquired after the write because another process could be waiting to overwrite the file.

    It is not released to make sure that the same piece of code that was written is also required.

    However, this whole thing is questionable to begin with.

    Why do you need to write a file to just later require it back in?