From d9473781f9934d8369e05b53f870c3f59132627d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sat, 18 May 2019 23:03:03 +0200 Subject: [PATCH] Do not start idle timer when lazy connection is already closed When an operation fails because the underlying connection is closed, we should never start an idle timer. There used to be a race condition that the connection close event was detected before cancelling the pending commands. We avoid this by checking the connection state before starting an idle timer when an operation fails. --- src/Io/LazyConnection.php | 2 +- tests/Io/LazyConnectionTest.php | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Io/LazyConnection.php b/src/Io/LazyConnection.php index 2dc35f2..3f5d69f 100644 --- a/src/Io/LazyConnection.php +++ b/src/Io/LazyConnection.php @@ -89,7 +89,7 @@ private function idle() { --$this->pending; - if ($this->pending < 1 && $this->idlePeriod >= 0) { + if ($this->pending < 1 && $this->idlePeriod >= 0 && $this->connecting !== null) { $this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () { $this->connecting->then(function (ConnectionInterface $connection) { $this->disconnecting = $connection; diff --git a/tests/Io/LazyConnectionTest.php b/tests/Io/LazyConnectionTest.php index 18e9c22..7f4dac9 100644 --- a/tests/Io/LazyConnectionTest.php +++ b/tests/Io/LazyConnectionTest.php @@ -513,6 +513,29 @@ public function testPingWillRejectAndStartTimerWhenPingFromUnderlyingConnectionR $ret->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); } + public function testPingWillRejectAndNotStartIdleTimerWhenPingFromUnderlyingConnectionRejectsBecauseConnectionIsDead() + { + $error = new \RuntimeException(); + + $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(array('ping', 'close'))->disableOriginalConstructor()->getMock(); + $base->expects($this->once())->method('ping')->willReturnCallback(function () use ($base, $error) { + $base->emit('close'); + return \React\Promise\reject($error); + }); + $base->expects($this->never())->method('close'); + + $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); + + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $loop->expects($this->never())->method('addTimer'); + + $connection = new LazyConnection($factory, '', $loop); + + $ret = $connection->ping(); + $ret->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); + } + public function testQuitResolvesAndEmitsCloseImmediatelyWhenConnectionIsNotAlreadyPending() { $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock();