bug #41793 [Cache] handle prefixed redis connections when clearing pools (nicolas-grekas)

This PR was merged into the 4.4 branch.

Discussion
----------

[Cache] handle prefixed redis connections when clearing pools

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #41012
| License       | MIT
| Doc PR        | -

Commits
-------

7527b5a4f9 [Cache] handle prefixed redis connections when clearing pools
This commit is contained in:
Nicolas Grekas 2021-06-23 20:26:53 +02:00
commit 52ca881776
15 changed files with 69 additions and 14 deletions

View File

@ -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'));
}
}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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()

View File

@ -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;

View File

@ -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_');
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -45,4 +45,9 @@ class RedisClusterNodeProxy
{
return $this->redis->scan($iIterator, $this->host, $strPattern, $iCount);
}
public function getOption($name)
{
return $this->redis->getOption($name);
}
}

View File

@ -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);