From 7527b5a4f9ca79aac6dd9525f3c123ba992988d7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 22 Jun 2021 17:28:51 +0200 Subject: [PATCH] [Cache] handle prefixed redis connections when clearing pools --- .../Tests/Adapter/AbstractRedisAdapterTest.php | 16 +++++++++++++++- .../Cache/Tests/Adapter/PredisAdapterTest.php | 2 +- .../Tests/Adapter/PredisClusterAdapterTest.php | 2 +- .../Adapter/PredisRedisClusterAdapterTest.php | 2 +- .../Tests/Adapter/PredisTagAwareAdapterTest.php | 2 +- .../Adapter/PredisTagAwareClusterAdapterTest.php | 2 +- .../Tests/Adapter/RedisAdapterSentinelTest.php | 2 +- .../Cache/Tests/Adapter/RedisAdapterTest.php | 8 ++++++-- .../Tests/Adapter/RedisArrayAdapterTest.php | 1 + .../Tests/Adapter/RedisClusterAdapterTest.php | 7 ++++++- .../Tests/Adapter/RedisTagAwareAdapterTest.php | 6 +++++- .../Adapter/RedisTagAwareArrayAdapterTest.php | 6 +++++- .../Adapter/RedisTagAwareClusterAdapterTest.php | 6 +++++- .../Cache/Traits/RedisClusterNodeProxy.php | 5 +++++ .../Component/Cache/Traits/RedisTrait.php | 16 +++++++++++++++- 15 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTest.php index 994ae81d5b..9d14007fde 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AbstractRedisAdapterTest.php @@ -24,7 +24,7 @@ abstract class AbstractRedisAdapterTest extends AdapterTestCase protected static $redis; - public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface { return new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); } @@ -45,4 +45,18 @@ abstract class AbstractRedisAdapterTest extends AdapterTestCase { self::$redis = null; } + + /** + * @runInSeparateProcess + */ + public function testClearWithPrefix() + { + $cache = $this->createCachePool(0, __FUNCTION__); + + $cache->save($cache->getItem('foo')->set('bar')); + $this->assertTrue($cache->hasItem('foo')); + + $cache->clear(); + $this->assertFalse($cache->hasItem('foo')); + } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterTest.php index e19f74f674..a1a2b4dda3 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PredisAdapterTest.php @@ -22,7 +22,7 @@ class PredisAdapterTest extends AbstractRedisAdapterTest public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - self::$redis = new \Predis\Client(['host' => getenv('REDIS_HOST')]); + self::$redis = new \Predis\Client(['host' => getenv('REDIS_HOST')], ['prefix' => 'prefix_']); } public function testCreateConnection() diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PredisClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PredisClusterAdapterTest.php index e6989be292..e2f09cd23a 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PredisClusterAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PredisClusterAdapterTest.php @@ -19,7 +19,7 @@ class PredisClusterAdapterTest extends AbstractRedisAdapterTest public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - self::$redis = new \Predis\Client([['host' => getenv('REDIS_HOST')]]); + self::$redis = new \Predis\Client([['host' => getenv('REDIS_HOST')]], ['prefix' => 'prefix_']); } public static function tearDownAfterClass(): void diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PredisRedisClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PredisRedisClusterAdapterTest.php index 81dd0bc2a0..9db83c0db4 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PredisRedisClusterAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PredisRedisClusterAdapterTest.php @@ -24,7 +24,7 @@ class PredisRedisClusterAdapterTest extends AbstractRedisAdapterTest self::markTestSkipped('REDIS_CLUSTER_HOSTS env var is not defined.'); } - self::$redis = RedisAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['class' => \Predis\Client::class, 'redis_cluster' => true]); + self::$redis = RedisAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['class' => \Predis\Client::class, 'redis_cluster' => true, 'prefix' => 'prefix_']); } public static function tearDownAfterClass(): void diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareAdapterTest.php index 6cffbde792..0971f80c55 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareAdapterTest.php @@ -27,7 +27,7 @@ class PredisTagAwareAdapterTest extends PredisAdapterTest $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface { $this->assertInstanceOf(\Predis\Client::class, self::$redis); $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php index 21120d606a..af25b2df52 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PredisTagAwareClusterAdapterTest.php @@ -27,7 +27,7 @@ class PredisTagAwareClusterAdapterTest extends PredisClusterAdapterTest $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface { $this->assertInstanceOf(\Predis\Client::class, self::$redis); $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php index 0eb407bafa..20f0811863 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterSentinelTest.php @@ -32,7 +32,7 @@ class RedisAdapterSentinelTest extends AbstractRedisAdapterTest self::markTestSkipped('REDIS_SENTINEL_SERVICE env var is not defined.'); } - self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['redis_sentinel' => $service]); + self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['redis_sentinel' => $service, 'prefix' => 'prefix_']); } public function testInvalidDSNHasBothClusterAndSentinel() diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php index b54a5acc84..d961187aec 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisAdapterTest.php @@ -28,9 +28,13 @@ class RedisAdapterTest extends AbstractRedisAdapterTest self::$redis = AbstractAdapter::createConnection('redis://'.getenv('REDIS_HOST'), ['lazy' => true]); } - public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface { - $adapter = parent::createCachePool($defaultLifetime); + if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { + self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); + } + + $adapter = parent::createCachePool($defaultLifetime, $testMethod); $this->assertInstanceOf(RedisProxy::class, self::$redis); return $adapter; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisArrayAdapterTest.php index 70afe6dac9..6e0b448746 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisArrayAdapterTest.php @@ -23,5 +23,6 @@ class RedisArrayAdapterTest extends AbstractRedisAdapterTest self::markTestSkipped('The RedisArray class is required.'); } self::$redis = new \RedisArray([getenv('REDIS_HOST')], ['lazy_connect' => true]); + self::$redis->setOption(\Redis::OPT_PREFIX, 'prefix_'); } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php index 1253aeb500..011a36b338 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisClusterAdapterTest.php @@ -32,10 +32,15 @@ class RedisClusterAdapterTest extends AbstractRedisAdapterTest } self::$redis = AbstractAdapter::createConnection('redis:?host['.str_replace(' ', ']&host[', $hosts).']', ['lazy' => true, 'redis_cluster' => true]); + self::$redis->setOption(\Redis::OPT_PREFIX, 'prefix_'); } - public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface { + if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { + self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); + } + $this->assertInstanceOf(RedisClusterProxy::class, self::$redis); $adapter = new RedisAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareAdapterTest.php index 5c82016be2..12e3b6ff55 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareAdapterTest.php @@ -28,8 +28,12 @@ class RedisTagAwareAdapterTest extends RedisAdapterTest $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface { + if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { + self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); + } + $this->assertInstanceOf(RedisProxy::class, self::$redis); $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php index 3ec500a901..b5823711dc 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareArrayAdapterTest.php @@ -27,8 +27,12 @@ class RedisTagAwareArrayAdapterTest extends RedisArrayAdapterTest $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface { + if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { + self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); + } + $this->assertInstanceOf(\RedisArray::class, self::$redis); $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php index 50f078c04d..d4a1bc9777 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/RedisTagAwareClusterAdapterTest.php @@ -28,8 +28,12 @@ class RedisTagAwareClusterAdapterTest extends RedisClusterAdapterTest $this->skippedTests['testTagItemExpiry'] = 'Testing expiration slows down the test suite'; } - public function createCachePool(int $defaultLifetime = 0): CacheItemPoolInterface + public function createCachePool(int $defaultLifetime = 0, string $testMethod = null): CacheItemPoolInterface { + if ('testClearWithPrefix' === $testMethod && \defined('Redis::SCAN_PREFIX')) { + self::$redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_PREFIX); + } + $this->assertInstanceOf(RedisClusterProxy::class, self::$redis); $adapter = new RedisTagAwareAdapter(self::$redis, str_replace('\\', '.', __CLASS__), $defaultLifetime); diff --git a/src/Symfony/Component/Cache/Traits/RedisClusterNodeProxy.php b/src/Symfony/Component/Cache/Traits/RedisClusterNodeProxy.php index 7818f0b8df..deba74f6a3 100644 --- a/src/Symfony/Component/Cache/Traits/RedisClusterNodeProxy.php +++ b/src/Symfony/Component/Cache/Traits/RedisClusterNodeProxy.php @@ -45,4 +45,9 @@ class RedisClusterNodeProxy { return $this->redis->scan($iIterator, $this->host, $strPattern, $iCount); } + + public function getOption($name) + { + return $this->redis->getOption($name); + } } diff --git a/src/Symfony/Component/Cache/Traits/RedisTrait.php b/src/Symfony/Component/Cache/Traits/RedisTrait.php index fcca8d652f..aaf4f1bd00 100644 --- a/src/Symfony/Component/Cache/Traits/RedisTrait.php +++ b/src/Symfony/Component/Cache/Traits/RedisTrait.php @@ -362,6 +362,11 @@ trait RedisTrait */ protected function doClear($namespace) { + if ($this->redis instanceof \Predis\ClientInterface) { + $prefix = $this->redis->getOptions()->prefix ? $this->redis->getOptions()->prefix->getPrefix() : ''; + $prefixLen = \strlen($prefix); + } + $cleared = true; $hosts = $this->getHosts(); $host = reset($hosts); @@ -379,7 +384,11 @@ trait RedisTrait $info = $host->info('Server'); $info = $info['Server'] ?? $info; - $pattern = $namespace.'*'; + if (!$host instanceof \Predis\ClientInterface) { + $prefix = \defined('Redis::SCAN_PREFIX') && (\Redis::SCAN_PREFIX & $host->getOption(\Redis::OPT_SCAN)) ? '' : $host->getOption(\Redis::OPT_PREFIX); + $prefixLen = \strlen($host->getOption(\Redis::OPT_PREFIX) ?? ''); + } + $pattern = $prefix.$namespace.'*'; if (!version_compare($info['redis_version'], '2.8', '>=')) { // As documented in Redis documentation (http://redis.io/commands/keys) using KEYS @@ -398,6 +407,11 @@ trait RedisTrait $keys = $keys[1]; } if ($keys) { + if ($prefixLen) { + foreach ($keys as $i => $key) { + $keys[$i] = substr($key, $prefixLen); + } + } $this->doDelete($keys); } } while ($cursor = (int) $cursor);