1

My centos cron-job code reads the local file and sometimes gets an empty string, but I never write an empty string to that file.

/*
 * in a cron job
 */

// verify that the file exists
if (!file_exists($my_file_path))        // if the file does Not exist
{                                       // then error
    error_log("file does Not exist");
    exit();
}

// read the file contents
$file_contents = file_get_contents($my_file_path);

// check result
if ($file_contents === false)           // if read failed
{                                       // then error
    error_log("file_contents === false");
    exit();
}

// look for empty string
if ($file_contents === '')              // if contents is empty string
{                                       // then check again after sleep
    error_log("file_contents === ''");
    sleep(1);
    $file_contents = file_get_contents($my_file_path);
    if ($file_contents === false)           // if read failed
    {                                       // then error
        error_log("file_contents === false, after sleep");
        exit();
    }
    if ($file_contents === '')              // if contents is still empty
    {                                       // then log it and exit
        error_log("file_contents is still empty, after sleep");
        exit();
    }
    else                                    // else contents Now Exists
    {                                       // so log it and use contents
        error_log("file_contents Exists, after sleep: $file_contents");
    }
}

In two places I write to the file:

// write the file contents
file_put_contents($my_file_path, time());

In one place I can remove the file (but I checked, and this code is Not being executed):

// remove the file
unlink($my_file_path);

I can't see how file_get_contents should ever give me an empty string, even if it was being unlinked.

  • `var_dump` will show the return. As manual states `Warning This function may return Boolean false, but may also return a non-Boolean value which evaluates to false. Please read the section on Booleans for more information. Use the === operator for testing the return value of this function.` – user3783243 Jan 19 '21 at 21:51
  • `0` would evaluate to `FALSE`, why not just look at what you actually have? – user3783243 Jan 19 '21 at 22:09
  • I did a `var_dump` of the result from the file_get_contents: `string(0) ""` – John Mullaney Jan 20 '21 at 00:18
  • I added code to check the contents a 2nd time, after a sleep of 1 second. The second time the contents **_is_** there, i.e., the file contains the timestamp I expect. I took out the sleep and that works too. The content is there if I do a 2nd file_get_contents. – John Mullaney Jan 20 '21 at 13:41
  • Race condition, use flock https://stackoverflow.com/a/48529209/2494754 – NVRM Jan 20 '21 at 14:12
  • 1
    Thanks. TIL that file_put_contents is not atomic, even when just writing/replacing and not appending. – John Mullaney Jan 20 '21 at 15:30
  • As you seen, a little tempo like `usleep(10000) ` is usually fine. Use flock if you have a lot of traffic. – NVRM Jan 20 '21 at 23:22
  • Since I was just using the file to store the current timestamp, I changed my code to use `touch()` to set the timestamp and `filemtime()` to read the timestamp. These **_are_** atomic operations, so I don't get in to trouble. – John Mullaney Jan 21 '21 at 16:44

2 Answers2

1

Try using the complete path to your file /var/www/html/.... /your_doc.php this can help you

user3783243
  • 5,368
  • 5
  • 22
  • 41
1

NVRM had the answer. I had a race condition between the file_put_contents() operation and the file_get_contents() operation. I did not know that the file_put_contents() was not an atomic operation, even when it is "write" and not "append".

There are a number of ways I could have fixed my code. Here's 2:

  • use flock
  • write to a temporary file and then use rename (because rename is ataomic)

Mine is a special, very simple, case where I just need to share a timestamp between my webpage scripts and a cron-job. So I ditched the use of file_put_contents() and file_get_contents() and replaced that code with touch() and filemtime().