Skip to content

Commit cc46375

Browse files
committed
saveCache() is atomic, alternative solution [Closes #11]
1 parent 01c0c64 commit cc46375

File tree

2 files changed

+47
-14
lines changed

2 files changed

+47
-14
lines changed

src/RobotLoader/RobotLoader.php

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -411,36 +411,51 @@ public function setTempDirectory(string $dir): self
411411
private function loadCache(): void
412412
{
413413
$file = $this->getCacheFile();
414-
[$this->classes, $this->missing] = @include $file; // @ file may not exist
415-
if (is_array($this->classes)) {
414+
$handle = fopen($file, 'cb+');
415+
if (!$handle || !flock($handle, LOCK_SH)) {
416+
throw new \RuntimeException("Unable to create or acquire shared lock on file '$file'.");
417+
}
418+
419+
$data = include $file;
420+
if (is_array($data)) {
421+
[$this->classes, $this->missing] = $data;
416422
return;
417423
}
418424

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

424-
[$this->classes, $this->missing] = @include $file; // @ file may not exist
425-
if (!is_array($this->classes)) {
426-
$this->rebuild();
429+
// while waiting for the lock, someone might have already created the cache
430+
if (fstat($handle)['size']) {
431+
flock($handle, LOCK_SH);
432+
$data = include $file;
433+
if (is_array($data)) {
434+
[$this->classes, $this->missing] = $data;
435+
return;
436+
}
427437
}
428438

429-
flock($handle, LOCK_UN);
430-
fclose($handle);
431-
@unlink("$file.lock"); // @ file may become locked on Windows
439+
$this->classes = $this->missing = [];
440+
$this->refreshClasses();
441+
$this->saveCache($handle);
432442
}
433443

434444

435445
/**
436446
* Writes class list to cache.
437447
*/
438-
private function saveCache(): void
448+
private function saveCache($handle = null): void
439449
{
440450
$file = $this->getCacheFile();
451+
$handle = $handle ?: fopen($file, 'cb+');
452+
if (!$handle || !flock($handle, LOCK_EX)) {
453+
throw new \RuntimeException("Unable to create or acquire exclusive lock on file '$file'.");
454+
}
455+
441456
$code = "<?php\nreturn " . var_export([$this->classes, $this->missing], true) . ";\n";
442-
if (file_put_contents("$file.tmp", $code) !== strlen($code) || !rename("$file.tmp", $file)) {
443-
@unlink("$file.tmp"); // @ - file may not exist
457+
if (!ftruncate($handle, 0) || fwrite($handle, $code) !== strlen($code)) {
458+
@unlink($file); // @ - the locked file may not be deleted
444459
throw new \RuntimeException("Unable to create '$file'.");
445460
}
446461
if (function_exists('opcache_invalidate')) {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
/**
4+
* @multiple 50
5+
*/
6+
7+
declare(strict_types = 1);
8+
9+
require __DIR__ . '/../../vendor/autoload.php';
10+
11+
$loader = new Nette\Loaders\RobotLoader;
12+
$loader->setAutoRefresh(true);
13+
$loader->setTempDirectory(__DIR__ . '/../tmp');
14+
$loader->addDirectory(__DIR__);
15+
$loader->register();
16+
17+
assert(class_exists(Foo::class) === false);
18+
assert(class_exists(Unknown::class) === false);

0 commit comments

Comments
 (0)