8000 Fix dangling timer when lazy connection closes with pending commands · SimonFrings/reactphp-redis@6859bf7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6859bf7

Browse files
committed
Fix dangling timer when lazy connection closes with pending commands
This is a somewhat hidden and rare race condition. The faulty idle timer would be started when the underlying Redis connection closes while the lazy connection still has pending commands, such as a blocking BLPOP operation. The timer would trigger some timer after the connection has been closed and would try to close the connection again, thus referencing undefined variables and causing a hard error in this case. The idle timer should not be started when the connection is already closed.
1 parent 82ac345 commit 6859bf7

File tree

2 files changed

+28
-1
lines changed

2 files changed

+28
-1
lines changed

src/LazyClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public function idle()
201201
{
202202
--$this->pending;
203203

204-
if ($this->pending < 1 && $this->idlePeriod >= 0 && !$this->subscribed && !$this->psubscribed) {
204+
if ($this->pending < 1 && $this->idlePeriod >= 0 && !$this->subscribed && !$this->psubscribed && $this->promise !== null) {
205205
$idleTimer =& $this->idleTimer;
206206
$promise =& $this->promise;
207207
$idleTimer = $this->loop->addTimer($this->idlePeriod, function () use (&$idleTimer, &$promise) {

tests/LazyClientTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,33 @@ public function testUnsubscribeAfterSubscribeWillResolveWhenUnderlyingClientReso
573573
$promise->then($this->expectCallableOnceWith(array('unsubscribe', 'foo', 0)));
574574
}
575575

576+
public function testBlpopWillRejectWhenUnderlyingClientClosesWhileWaitingForResponse()
577+
{
578+
$closeHandler = null;
579+
$deferred = new Deferred();
580+
$client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock();
581+
$client->expects($this->once())->method('__call')->with('blpop')->willReturn($deferred->promise());
582+
$client->expects($this->any())->method('on')->withConsecutive(
583+
array('close', $this->callback(function ($arg) use (&$closeHandler) {
584+
$closeHandler = $arg;
585+
return true;
586+
}))
587+
);
588+
589+
$this->factory->expects($this->once())->method('createClient')->willReturn(\React\Promise\resolve($client));
590+
591+
$this->loop->expects($this->never())->method('addTimer');
592+
593+
$promise = $this->client->blpop('list');
594+
595+
$this->assertTrue(is_callable($closeHandler));
596+
$closeHandler();
597+
598+
$deferred->reject($e = new \RuntimeException());
599+
600+
$promise->then(null, $this->expectCallableOnceWith($e));
601+
}
602+
576603
public function createCallableMockWithOriginalConstructorDisabled($array)
577604
{
578605
if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) {

0 commit comments

Comments
 (0)
0