Skip to content

Instantly share code, notes, and snippets.

@videni
Last active July 18, 2023 08:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save videni/33c00216b0bec5498d36c1130899d235 to your computer and use it in GitHub Desktop.
Save videni/33c00216b0bec5498d36c1130899d235 to your computer and use it in GitHub Desktop.
php-save-load-using-require-method-race condition-demo
<?php
$cachePath = '/tmp/t.php';
$put = function($cachePath){
$code = '<?php';
$code .= "\n\n";
$code .= 'return ' . var_export([], true) . ';';
file_put_contents($cachePath, $code, LOCK_EX);
};
$get = function($cachePath) {
return require($cachePath);
};
$retry = function($cachePath) use($get) {
$start = microtime (true);
$count = 0;
do {
$result = $get($cachePath);
// Race condition happens when $result is not array.
if (is_array($result)) {
if ($count >0) {
echo sprintf("%s => %s micro seconds".PHP_EOL, $count, (microtime(true) - $start) * 1000000);
}
return $result;
}
$count++;
} while($result === 1 );
};
$load = function($cachePath) use($retry, $put) {
$put($cachePath); // Create the file every time, so we can see the race condition exists
return $retry($cachePath);
};
for($i=0;$i<1000000;$i++) {
$load($cachePath);
}
@videni
Copy link
Author

videni commented Jul 17, 2023

run the above script by two php processes.

@videni
Copy link
Author

videni commented Jul 17, 2023

Handle race condition. In my case, the cache file won't change frequently, so read it until the data is what we want. You can think it as spinlock. it may execute 1-10 times(more if better IO) till the data is ready. if you got better solution, let me know please, send me an email.
Read Time-of-check_to_time-of-use for why there is a race condition,

@videni
Copy link
Author

videni commented Jul 18, 2023

The loop woule take 22 micro seconds , which is acceptable in my case, but a better solution is to diable the truncation problem, check here

@videni
Copy link
Author

videni commented Jul 18, 2023

New way

<?php

$cachePath = '/tmp/t.php';

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

    $fp = fopen($cachePath, "c");

    flock($fp, LOCK_EX);
    fwrite($fp, $code);

    fclose($fp);
};

$get = function($cachePath) {
    return require($cachePath);
};

$load = function($cachePath) use($get, $put) {
    $put($cachePath); // Create the file every time, so we can see the race condition exists

    var_dump($get($cachePath));
};

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment