diff --git a/CHANGELOG.md b/CHANGELOG.md index 18164aa3..e290577d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Added - Make ZMSCORE command Prefixable and add to a ClusterStrategy (#1692) +### Fixed +- Fixed Sentinel does not wipe servers on exception caused (#1694) + ## v3.5.1 (2026-06-11) ### Added - Expose `pipeline()` API via `ClientInterface` (#1686) diff --git a/src/Connection/Replication/SentinelReplication.php b/src/Connection/Replication/SentinelReplication.php index 4a5099f8..af4376f6 100644 --- a/src/Connection/Replication/SentinelReplication.php +++ b/src/Connection/Replication/SentinelReplication.php @@ -33,6 +33,7 @@ use Predis\Response\ErrorInterface as ErrorResponseInterface; use Predis\Response\ServerException; use Predis\Retry\Retry; use Predis\Retry\Strategy\ExponentialBackoff; +use Throwable; /** * @author Daniele Alessandri @@ -753,9 +754,12 @@ class SentinelReplication extends AbstractAggregateConnection implements Replica return $response; }; - $failCallback = function (CommunicationException $exception) { + $failCallback = function (Throwable $exception) { $this->wipeServerList(); - $exception->getConnection()->disconnect(); + + if ($exception instanceof CommunicationException) { + $exception->getConnection()->disconnect(); + } }; return $retry->callWithRetry($doCallback, $failCallback); diff --git a/tests/Predis/Connection/Replication/SentinelReplicationTest.php b/tests/Predis/Connection/Replication/SentinelReplicationTest.php index 9aed6358..0b04d0b5 100644 --- a/tests/Predis/Connection/Replication/SentinelReplicationTest.php +++ b/tests/Predis/Connection/Replication/SentinelReplicationTest.php @@ -17,6 +17,7 @@ use Predis\Command; use Predis\Connection; use Predis\Connection\Parameters; use Predis\Connection\ParametersInterface; +use Predis\Connection\Resource\Exception\StreamInitException; use Predis\Connection\Resource\StreamFactoryInterface; use Predis\Connection\StreamConnection; use Predis\Replication; @@ -1496,6 +1497,73 @@ class SentinelReplicationTest extends PredisTestCase )); } + /** + * @group disconnected + */ + public function testMethodExecuteCommandRetriesWriteCommandOnNewMasterOnStreamInitException(): void + { + $sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel'); + $sentinel1 + ->expects($this->any()) + ->method('executeCommand') + ->with($this->isRedisCommand( + 'SENTINEL', ['get-master-addr-by-name', 'svc'] + )) + ->willReturn( + ['127.0.0.1', '6391'] + ); + + $masterOld = $this->getMockConnection('tcp://127.0.0.1:6381?role=master'); + $masterOld + ->expects($this->any()) + ->method('isConnected') + ->willReturn(true); + $masterOld + ->expects($this->once()) + ->method('executeCommand') + ->with( + $this->isRedisCommand('DEL', ['key']) + ) + ->willThrowException( + new StreamInitException('Connection refused [tcp://127.0.0.1:6381]') + ); + + $masterNew = $this->getMockConnection('tcp://127.0.0.1:6391?role=master'); + $masterNew + ->expects($this->any()) + ->method('isConnected') + ->willReturn(true); + $masterNew + ->expects($this->once()) + ->method('executeCommand') + ->withConsecutive( + [$this->isRedisCommand('DEL', ['key'])] + ) + ->willReturnOnConsecutiveCalls( + 1 + ); + + /** @var Connection\FactoryInterface|MockObject */ + $factory = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock(); + $factory + ->expects($this->once()) + ->method('create') + ->with([ + 'host' => '127.0.0.1', + 'port' => '6391', + 'role' => 'master', + ]) + ->willReturn($masterNew); + + $replication = $this->getReplicationConnection('svc', [$sentinel1], $factory); + + $replication->add($masterOld); + + $this->assertSame(1, $replication->executeCommand( + Command\RawCommand::create('del', 'key') + )); + } + /** * @group disconnected */