From 0302b1d8b057777e13a375c1a5075b03947b0335 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 9 Oct 2019 10:18:57 +0200 Subject: [PATCH] [Cache] improve perf of pruning for fs-based adapters --- .../Adapter/FilesystemTagAwareAdapter.php | 54 ++++++++++++++++++- .../Cache/Traits/FilesystemCommonTrait.php | 33 ++++++++++-- .../Cache/Traits/FilesystemTrait.php | 2 +- .../Component/Cache/Traits/PhpFilesTrait.php | 2 +- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php index 69548e0946..82c3960a84 100644 --- a/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/FilesystemTagAwareAdapter.php @@ -25,6 +25,7 @@ use Symfony\Component\Cache\Traits\FilesystemTrait; class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements PruneableInterface { use FilesystemTrait { + doClear as private doClearCache; doSave as private doSaveCache; doDelete as private doDeleteCache; } @@ -41,6 +42,55 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune $this->init($namespace, $directory); } + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + $ok = $this->doClearCache($namespace); + + if ('' !== $namespace) { + return $ok; + } + + set_error_handler(static function () {}); + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + try { + foreach ($this->scanHashDir($this->directory.self::TAG_FOLDER.\DIRECTORY_SEPARATOR) as $dir) { + if (rename($dir, $renamed = substr_replace($dir, bin2hex(random_bytes(4)), -8))) { + $dir = $renamed.\DIRECTORY_SEPARATOR; + } else { + $dir .= \DIRECTORY_SEPARATOR; + $renamed = null; + } + + for ($i = 0; $i < 38; ++$i) { + if (!file_exists($dir.$chars[$i])) { + continue; + } + for ($j = 0; $j < 38; ++$j) { + if (!file_exists($d = $dir.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + foreach (scandir($d, SCANDIR_SORT_NONE) ?: [] as $link) { + if ('.' !== $link && '..' !== $link && (null !== $renamed || !realpath($d.\DIRECTORY_SEPARATOR.$link))) { + unlink($d.\DIRECTORY_SEPARATOR.$link); + } + } + null === $renamed ?: rmdir($d); + } + null === $renamed ?: rmdir($dir.$chars[$i]); + } + null === $renamed ?: rmdir($renamed); + } + } finally { + restore_error_handler(); + } + + return $ok; + } + /** * {@inheritdoc} */ @@ -111,13 +161,13 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune set_error_handler(static function () {}); try { - if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -1))) { + if (rename($tagFolder, $renamed = substr_replace($tagFolder, bin2hex(random_bytes(4)), -9))) { $tagFolder = $renamed.\DIRECTORY_SEPARATOR; } else { $renamed = null; } - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($tagFolder, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $itemLink) { + foreach ($this->scanHashDir($tagFolder) as $itemLink) { unlink(realpath($itemLink) ?: $itemLink); unlink($itemLink); } diff --git a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php index 0e183ced99..d828982b82 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php @@ -26,7 +26,7 @@ trait FilesystemCommonTrait private function init(string $namespace, ?string $directory) { if (!isset($directory[0])) { - $directory = sys_get_temp_dir().'/symfony-cache'; + $directory = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony-cache'; } else { $directory = realpath($directory) ?: $directory; } @@ -55,12 +55,12 @@ trait FilesystemCommonTrait { $ok = true; - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) { + foreach ($this->scanHashDir($this->directory) as $file) { if ('' !== $namespace && 0 !== strpos($this->getFileKey($file), $namespace)) { continue; } - $ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok; + $ok = ($this->doUnlink($file) || !file_exists($file)) && $ok; } return $ok; @@ -123,6 +123,33 @@ trait FilesystemCommonTrait return ''; } + private function scanHashDir(string $directory): \Generator + { + if (!file_exists($directory)) { + return; + } + + $chars = '+-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + for ($i = 0; $i < 38; ++$i) { + if (!file_exists($directory.$chars[$i])) { + continue; + } + + for ($j = 0; $j < 38; ++$j) { + if (!file_exists($dir = $directory.$chars[$i].\DIRECTORY_SEPARATOR.$chars[$j])) { + continue; + } + + foreach (@scandir($dir, SCANDIR_SORT_NONE) ?: [] as $file) { + if ('.' !== $file && '..' !== $file) { + yield $dir.\DIRECTORY_SEPARATOR.$file; + } + } + } + } + } + /** * @internal */ diff --git a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php index 31f0d74372..185eb0006d 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php @@ -33,7 +33,7 @@ trait FilesystemTrait $time = time(); $pruned = true; - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + foreach ($this->scanHashDir($this->directory) as $file) { if (!$h = @fopen($file, 'rb')) { continue; } diff --git a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php index baacefc152..41ff8bdd07 100644 --- a/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php +++ b/src/Symfony/Component/Cache/Traits/PhpFilesTrait.php @@ -54,7 +54,7 @@ trait PhpFilesTrait set_error_handler($this->includeHandler); try { - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + foreach ($this->scanHashDir($this->directory) as $file) { try { if (\is_array($expiresAt = include $file)) { $expiresAt = $expiresAt[0];