8000 Merge pull request #86 from clue-labs/timeout · SimonFrings/reactphp-redis@4e28607 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4e28607

Browse files
authored
Merge pull request clue#86 from clue-labs/timeout
Support connection timeouts
2 parents 3d0ae7e + 7f73a13 commit 4e28607

File tree

4 files changed

+81
-3
lines changed

4 files changed

+81
-3
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,15 @@ $factory->createClient('redis+unix:///tmp/redis.sock?password=secret&db=2');
186186
$factory->createClient('redis+unix://:secret@/tmp/redis.sock');
187187
```
188188

189+
This method respects PHP's `default_socket_timeout` setting (default 60s)
190+
as a timeout for establishing the connection and waiting for successful
191+
authentication. You can explicitly pass a custom timeout value in seconds
192+
(or use a negative number to not apply a timeout) like this:
193+
194+
```php
195+
$factory->createClient('localhost?timeout=0.5');
196+
```
197+
189198
### Client
190199

191200
The `Client` is responsible for exchanging messages with Redis

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
1717
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
1818
"react/promise": "^2.0 || ^1.1",
19+
"react/promise-timer": "^1.5",
1920
"react/socket": "^1.1"
2021
},
2122
"autoload": {

src/Factory.php

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44

55
use Clue\Redis\Protocol\Factory as ProtocolFactory;
66
use React\EventLoop\LoopInterface;
7-
use React\Promise;
87
use React\Promise\Deferred;
8+
use React\Promise\Timer\TimeoutException;
99
use React\Socket\ConnectionInterface;
1010
use React\Socket\Connector;
1111
use React\Socket\ConnectorInterface;
1212
use InvalidArgumentException;
1313

1414
class Factory
1515
{
16+
private $loop;
1617
private $connector;
1718
private $protocol;
1819

@@ -32,6 +33,7 @@ public function __construct(LoopInterface $loop, ConnectorInterface $connector =
3233
$protocol = new ProtocolFactory();
3334
}
3435

36+
$this->loop = $loop;
3537
$this->connector = $connector;
3638
$this->protocol = $protocol;
3739
}
@@ -47,7 +49,7 @@ public function createClient($target)
4749
try {
4850
$parts = $this->parseUrl($target);
4951
} catch (InvalidArgumentException $e) {
50-
return Promise\reject($e);
52+
return \React\Promise\reject($e);
5153
}
5254

5355
$connecting = $this->connector->connect($parts['authority']);
@@ -97,7 +99,20 @@ function ($error) use ($client) {
9799

98100
$promise->then(array($deferred, 'resolve'), array($deferred, 'reject'));
99101

100-
return $deferred->promise();
102+
// use timeout from explicit ?timeout=x parameter or default to PHP's default_socket_timeout (60)
103+
$timeout = (float) isset($parts['timeout']) ? $parts['timeout'] : ini_get("default_socket_timeout");
104+
if ($timeout < 0) {
105+
return $deferred->promise();
106+
}
107+
108+
return \React\Promise\Timer\timeout($deferred->promise(), $timeout, $this->loop)->then(null, function ($e) {
109+
if ($e instanceof TimeoutException) {
110+
throw new \RuntimeException(
111+
'Connection to database server timed out after ' . $e->getTimeout() . ' seconds'
112+
);
113+
}
114+
throw $e;
115+
});
101116
}
102117

103118
/**
@@ -150,6 +165,10 @@ private function parseUrl($target)
150165
if (isset($args['db'])) {
151166
$ret['db'] = $args['db'];
152167
}
168+
169+
if (isset($args['timeout'])) {
170+
$ret['timeout'] = $args['timeout'];
171+
}
153172
}
154173

155174
return $ret;

tests/FactoryTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,53 @@ public function testCancelWillCloseConnectionWhenConnectionWaitsForSelect()
186186
$promise = $this->factory->createClient('redis://127.0.0.1:2/123');
187187
$promise->cancel();
188188
}
189+
190+
public function testCreateClientWithTimeoutParameterWillStartTimerAndRejectOnExplicitTimeout()
191+
{
192+
$timeout = null;
193+
$this->loop->expects($this->once())->method('addTimer')->with(0, $this->callback(function ($cb) use (&$timeout) {
194+
$timeout = $cb;
195+
return true;
196+
}));
197+
198+
$deferred = new Deferred();
199+
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:2')->willReturn($deferred->promise());
200+
201+
$promise = $this->factory->createClient('redis://127.0.0.1:2?timeout=0');
202+
203+
$this->assertNotNull($timeout);
204+
$timeout();
205+
206+
$promise->then(null, $this->expectCallableOnceWith(
207+
$this->logicalAnd(
208+
$this->isInstanceOf('Exception'),
209+
$this->callback(function (\Exception $e) {
210+
return $e->getMessage() === 'Connection to database server timed out after 0 seconds';
211+
})
212+
)
213+
));
214+
}
215+
216+
public function testCreateClientWithNegativeTimeoutParameterWillNotStartTimer()
217+
{
218+
$this->loop->expects($this->never())->method('addTimer');
219+
220+
$deferred = new Deferred();
221+
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:2')->willReturn($deferred->promise());
222+
223+
$this->factory->createClient('redis://127.0.0.1:2?timeout=-1');
224+
}
225+
226+
public function testCreateClientWithoutTimeoutParameterWillStartTimerWithDefaultTimeoutFromIni()
227+
{
228+
$this->loop->expects($this->once())->method('addTimer')->with(1.5, $this->anything());
229+
230+
$deferred = new Deferred();
231+
$this->connector->expects($this->once())->method('connect')->with('127.0.0.1:2')->willReturn($deferred->promise());
232+
233+
$old = ini_get('default_socket_timeout');
234+
ini_set('default_socket_timeout', '1.5');
235+
$this->factory->createClient('redis://127.0.0.1:2');
236+
ini_set('default_socket_timeout', $old);
237+
}
189238
}

0 commit comments

Comments
 (0)
0