Treat LOADING Errors as Connection Failures to Retry with Next Replica (#1536)

* fix(sentinel-replication): Treat LOADING error responses as ConnectionException to allow retry mechanism to discard the current replica and switch to the next available one.

* fix(sentinel-replication): add changelog entries for LOADING response handling

* Update CHANGELOG.md

* Update CHANGELOG.md

---------

Co-authored-by: Mustafa AKBEL <mustafa.akbel@pusula.net.tr>
Co-authored-by: Till Krüss <tillkruss@users.noreply.github.com>
This commit is contained in:
Mustafa AKBEL
2025-05-28 15:11:13 +03:00
committed by GitHub
parent 2372b5c767
commit 6021daf9cd
3 changed files with 91 additions and 0 deletions
+3
View File
@@ -1,6 +1,9 @@
## Changelog
## Unreleased
### Changed
- Handle and retry `LOADING` errors from Sentinel replicas (#1536)
### Fixed
- Fixed PHP 8.4 deprecated call to `stream_context_set_option()` (#1545)
- Fixed return type for `ZCOUNT` to be `int` (#1547)
@@ -715,6 +715,9 @@ class SentinelReplication extends AbstractAggregateConnection implements Replica
while ($retries <= $this->retryLimit) {
try {
$response = $this->getConnectionByCommand($command)->$method($command);
if ($response instanceof Error && $response->getErrorType() === 'LOADING') {
throw new ConnectionException($this->current, $response->getMessage());
}
break;
} catch (CommunicationException $exception) {
$this->wipeServerList();
@@ -1599,6 +1599,91 @@ class SentinelReplicationTest extends PredisTestCase
];
}
/**
* @group disconnected
*/
public function testDiscardsSlaveWhenRespondsLOADINGAndExecutesReadOnlyCommandOnNextSlave(): void
{
$sentinel1 = $this->getMockSentinelConnection('tcp://127.0.0.1:5381?role=sentinel');
$sentinel1
->expects($this->any())
->method('executeCommand')
->with($this->isRedisCommand(
'SENTINEL', ['slaves', 'svc']
))
->willReturn(
[
[
'name', '127.0.0.1:6383',
'ip', '127.0.0.1',
'port', '6383',
'runid', '1c0bf1291797fbc5608c07a17da394147dc62817',
'flags', 'slave',
'master-host', '127.0.0.1',
'master-port', '6381',
],
]
);
$master = $this->getMockConnection('tcp://127.0.0.1:6381?role=master');
$master
->expects($this->any())
->method('isConnected')
->willReturn(true);
$slave1 = $this->getMockConnection('tcp://127.0.0.1:6382?role=slave');
$slave1
->expects($this->any())
->method('isConnected')
->willReturn(true);
$slave1
->expects($this->once())
->method('executeCommand')
->with(
$this->isRedisCommand('GET', ['key'])
)
->willReturn(
new Response\Error('LOADING')
);
$slave2 = $this->getMockConnection('tcp://127.0.0.1:6383?role=slave');
$slave2
->expects($this->any())
->method('isConnected')
->willReturn(true);
$slave2
->expects($this->once())
->method('executeCommand')
->withConsecutive(
[$this->isRedisCommand('GET', ['key'])]
)
->willReturnOnConsecutiveCalls(
'value'
);
/** @var Connection\FactoryInterface|MockObject */
$factory = $this->getMockBuilder('Predis\Connection\FactoryInterface')->getMock();
$factory
->expects($this->once())
->method('create')
->with([
'host' => '127.0.0.1',
'port' => '6383',
'role' => 'slave',
])
->willReturn($slave2);
$replication = $this->getReplicationConnection('svc', [$sentinel1], $factory);
$replication->add($master);
$replication->add($slave1);
$this->assertSame('value', $replication->executeCommand(
Command\RawCommand::create('get', 'key')
));
$this->assertSame($slave2, $replication->getCurrent());
}
// ******************************************************************** //
// ---- HELPER METHODS ------------------------------------------------ //
// ******************************************************************** //