Skip to content
Permalink
Browse files

saveCache() is atomic, alternative solution [Closes #11]

  • Loading branch information
dg committed Feb 12, 2020
1 parent 01c0c64 commit bfb7bf036d16c517d2a7011e7512a6adeb0d6057
Showing with 44 additions and 13 deletions.
  1. +26 −13 src/RobotLoader/RobotLoader.php
  2. +18 −0 tests/Loaders/RobotLoader.stress.phpt
@@ -411,36 +411,49 @@ public function setTempDirectory(string $dir): self
private function loadCache(): void
{
$file = $this->getCacheFile();
[$this->classes, $this->missing] = @include $file; // @ file may not exist
$handle = fopen($file, 'cb+');
if (!$handle || !flock($handle, LOCK_SH)) {
throw new \RuntimeException("Unable to create or acquire shared lock on file '$file'.");
}

[$this->classes, $this->missing] = include $file;
if (is_array($this->classes)) {
return;
}

$handle = fopen("$file.lock", 'cb+');
if (!$handle || !flock($handle, LOCK_EX)) {
throw new \RuntimeException("Unable to create or acquire exclusive lock on file '$file.lock'.");
if (!flock($handle, LOCK_EX)) {
throw new \RuntimeException("Unable to create or acquire exclusive lock on file '$file'.");
}

[$this->classes, $this->missing] = @include $file; // @ file may not exist
if (!is_array($this->classes)) {
$this->rebuild();
// while waiting for the lock, someone might have already created the cache
if (fstat($handle)['size']) {
flock($handle, LOCK_SH);
[$this->classes, $this->missing] = include $file;
if (is_array($this->classes)) {
return;
}
}

flock($handle, LOCK_UN);
fclose($handle);
@unlink("$file.lock"); // @ file may become locked on Windows
$this->classes = $this->missing = [];
$this->refreshClasses();
$this->saveCache($handle);
}


/**
* Writes class list to cache.
*/
private function saveCache(): void
private function saveCache($handle = null): void
{
$file = $this->getCacheFile();
$handle = $handle ?: fopen($file, 'cb+');
if (!$handle || !flock($handle, LOCK_EX)) {
throw new \RuntimeException("Unable to create or acquire exclusive lock on file '$file'.");
}

$code = "<?php\nreturn " . var_export([$this->classes, $this->missing], true) . ";\n";
if (file_put_contents("$file.tmp", $code) !== strlen($code) || !rename("$file.tmp", $file)) {
@unlink("$file.tmp"); // @ - file may not exist
if (!ftruncate($handle, 0) || fwrite($handle, $code) !== strlen($code)) {
@unlink($file); // @ - the locked file may not be deleted
throw new \RuntimeException("Unable to create '$file'.");
}
if (function_exists('opcache_invalidate')) {
@@ -0,0 +1,18 @@
<?php

/**
* @multiple 50
*/

declare(strict_types = 1);

require __DIR__ . '/../../vendor/autoload.php';

$loader = new Nette\Loaders\RobotLoader;
$loader->setAutoRefresh(true);
$loader->setTempDirectory(__DIR__ . '/../tmp');
$loader->addDirectory(__DIR__);
$loader->register();

assert(class_exists(Foo::class) === false);
assert(class_exists(Unknown::class) === false);

0 comments on commit bfb7bf0

Please sign in to comment.
You can’t perform that action at this time.