Skip to content

Commit ce4f815

Browse files
uncaughtnicolas-grekas
authored andcommitted
bug #51578 [Cache] always select database for persistent redis connections
1 parent 184597d commit ce4f815

File tree

2 files changed

+57
-16
lines changed

2 files changed

+57
-16
lines changed

src/Symfony/Component/Cache/Tests/Traits/RedisTraitTest.php

+53
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,57 @@ public static function provideCreateConnection(): array
7474
],
7575
];
7676
}
77+
78+
/**
79+
* Due to a bug in phpredis, the persistent connection will keep its last selected database. So when re-using
80+
* a persistent connection, the database has to be re-selected, too.
81+
*
82+
* @see https://github.com/phpredis/phpredis/issues/1920
83+
*
84+
* @group integration
85+
*/
86+
public function testPconnectSelectsCorrectDatabase()
87+
{
88+
if (!class_exists(\Redis::class)) {
89+
throw new SkippedTestSuiteError('The "Redis" class is required.');
90+
}
91+
if (!getenv('REDIS_HOST')) {
92+
throw new SkippedTestSuiteError('REDIS_HOST env var is not defined.');
93+
}
94+
if (!\ini_get('redis.pconnect.pooling_enabled')) {
95+
throw new SkippedTestSuiteError('The bug only occurs when pooling is enabled.');
96+
}
97+
98+
// Limit the connection pool size to 1:
99+
if (false === $prevPoolSize = ini_set('redis.pconnect.connection_limit', 1)) {
100+
throw new SkippedTestSuiteError('Unable to set pool size');
101+
}
102+
103+
try {
104+
$mock = self::getObjectForTrait(RedisTrait::class);
105+
106+
$dsn = 'redis://'.getenv('REDIS_HOST');
107+
108+
$cacheKey = 'testPconnectSelectsCorrectDatabase';
109+
$cacheValueOnDb1 = 'I should only be on database 1';
110+
111+
// First connect to database 1 and set a value there so we can identify this database:
112+
$db1 = $mock::createConnection($dsn, ['dbindex' => 1, 'persistent' => 1]);
113+
self::assertInstanceOf(\Redis::class, $db1);
114+
self::assertSame(1, $db1->getDbNum());
115+
$db1->set($cacheKey, $cacheValueOnDb1);
116+
self::assertSame($cacheValueOnDb1, $db1->get($cacheKey));
117+
118+
// Unset the connection - do not use `close()` or we will lose the persistent connection:
119+
unset($db1);
120+
121+
// Now connect to database 0 and see that we do not actually ended up on database 1 by checking the value:
122+
$db0 = $mock::createConnection($dsn, ['dbindex' => 0, 'persistent' => 1]);
123+
self::assertInstanceOf(\Redis::class, $db0);
124+
self::assertSame(0, $db0->getDbNum()); // Redis is lying here! We could actually be on any database!
125+
self::assertNotSame($cacheValueOnDb1, $db0->get($cacheKey)); // This value should not exist if we are actually on db 0
126+
} finally {
127+
ini_set('redis.pconnect.connection_limit', $prevPoolSize);
128+
}
129+
}
77130
}

src/Symfony/Component/Cache/Traits/RedisTrait.php

+4-16
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,10 @@ public static function createConnection(string $dsn, array $options = [])
283283
}
284284

285285
if ((null !== $auth && !$redis->auth($auth))
286-
|| ($params['dbindex'] && !$redis->select($params['dbindex']))
286+
// Due to a bug in phpredis we must always select the dbindex if persistent pooling is enabled
287+
// @see https://github.com/phpredis/phpredis/issues/1920
288+
// @see https://github.com/symfony/symfony/issues/51578
289+
|| (($params['dbindex'] || ('pconnect' === $connect && '0' !== \ini_get('redis.pconnect.pooling_enabled'))) && !$redis->select($params['dbindex']))
287290
) {
288291
$e = preg_replace('/^ERR /', '', $redis->getLastError());
289292
throw new InvalidArgumentException('Redis connection failed: '.$e.'.');
@@ -403,9 +406,6 @@ public static function createConnection(string $dsn, array $options = [])
403406
return $redis;
404407
}
405408

406-
/**
407-
* {@inheritdoc}
408-
*/
409409
protected function doFetch(array $ids)
410410
{
411411
if (!$ids) {
@@ -439,17 +439,11 @@ protected function doFetch(array $ids)
439439
return $result;
440440
}
441441

442-
/**
443-
* {@inheritdoc}
444-
*/
445442
protected function doHave(string $id)
446443
{
447444
return (bool) $this->redis->exists($id);
448445
}
449446

450-
/**
451-
* {@inheritdoc}
452-
*/
453447
protected function doClear(string $namespace)
454448
{
455449
if ($this->redis instanceof \Predis\ClientInterface) {
@@ -511,9 +505,6 @@ protected function doClear(string $namespace)
511505
return $cleared;
512506
}
513507

514-
/**
515-
* {@inheritdoc}
516-
*/
517508
protected function doDelete(array $ids)
518509
{
519510
if (!$ids) {
@@ -548,9 +539,6 @@ protected function doDelete(array $ids)
548539
return true;
549540
}
550541

551-
/**
552-
* {@inheritdoc}
553-
*/
554542
protected function doSave(array $values, int $lifetime)
555543
{
556544
if (!$values = $this->marshaller->marshall($values, $failed)) {

0 commit comments

Comments
 (0)