Skip to content

Commit d9b136b

Browse files
committed
Improve timeout error messages
1 parent cf7c46f commit d9b136b

File tree

4 files changed

+88
-37
lines changed

4 files changed

+88
-37
lines changed

Diff for: src/TimeoutConnector.php

+26-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use React\EventLoop\LoopInterface;
66
use React\Promise\Timer;
7+
use React\Promise\Timer\TimeoutException;
78

89
final class TimeoutConnector implements ConnectorInterface
910
{
@@ -20,6 +21,30 @@ public function __construct(ConnectorInterface $connector, $timeout, LoopInterfa
2021

2122
public function connect($uri)
2223
{
23-
return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop);
24+
return Timer\timeout($this->connector->connect($uri), $this->timeout, $this->loop)->then(null, self::handler($uri));
25+
}
26+
27+
/**
28+
* Creates a static rejection handler that reports a proper error message in case of a timeout.
29+
*
30+
* This uses a private static helper method to ensure this closure is not
31+
* bound to this instance and the exception trace does not include a
32+
* reference to this instance and its connector stack as a result.
33+
*
34+
* @param string $uri
35+
* @return callable
36+
*/
37+
private static function handler($uri)
38+
{
39+
return function (\Exception $e) use ($uri) {
40+
if ($e instanceof TimeoutException) {
41+
throw new \RuntimeException(
42+
'Connection to ' . $uri . ' timed out after ' . $e->getTimeout() . ' seconds',
43+
\defined('SOCKET_ETIMEDOUT') ? \SOCKET_ETIMEDOUT : 0
44+
);
45+
}
46+
47+
throw $e;
48+
};
2449
}
2550
}

Diff for: tests/DnsConnectorTest.php

-15
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,6 @@ public function testCancelDuringTcpConnectionCancelsTcpConnectionWithTcpRejectio
195195
$this->throwRejection($promise);
196196
}
197197

198-
/**
199-
* @requires PHP 7
200-
*/
201198
public function testRejectionDuringDnsLookupShouldNotCreateAnyGarbageReferences()
202199
{
203200
if (class_exists('React\Promise\When')) {
@@ -217,9 +214,6 @@ public function testRejectionDuringDnsLookupShouldNotCreateAnyGarbageReferences(
217214
$this->assertEquals(0, gc_collect_cycles());
218215
}
219216

220-
/**
221-
* @requires PHP 7
222-
*/
223217
public function testRejectionAfterDnsLookupShouldNotCreateAnyGarbageReferences()
224218
{
225219
if (class_exists('React\Promise\When')) {
@@ -242,9 +236,6 @@ public function testRejectionAfterDnsLookupShouldNotCreateAnyGarbageReferences()
242236
$this->assertEquals(0, gc_collect_cycles());
243237
}
244238

245-
/**
246-
* @requires PHP 7
247-
*/
248239
public function testRejectionAfterDnsLookupShouldNotCreateAnyGarbageReferencesAgain()
249240
{
250241
if (class_exists('React\Promise\When')) {
@@ -270,9 +261,6 @@ public function testRejectionAfterDnsLookupShouldNotCreateAnyGarbageReferencesAg
270261
$this->assertEquals(0, gc_collect_cycles());
271262
}
272263

273-
/**
274-
* @requires PHP 7
275-
*/
276264
public function testCancelDuringDnsLookupShouldNotCreateAnyGarbageReferences()
277265
{
278266
if (class_exists('React\Promise\When')) {
@@ -295,9 +283,6 @@ public function testCancelDuringDnsLookupShouldNotCreateAnyGarbageReferences()
295283
$this->assertEquals(0, gc_collect_cycles());
296284
}
297285

298-
/**
299-
* @requires PHP 7
300-
*/
301286
public function testCancelDuringTcpConnectionShouldNotCreateAnyGarbageReferences()
302287
{
303288
if (class_exists('React\Promise\When')) {

Diff for: tests/IntegrationTest.php

-6
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,6 @@ function ($e) use (&$wait) {
181181
$this->assertEquals(0, gc_collect_cycles());
182182
}
183183

184-
/**
185-
* @requires PHP 7
186-
*/
187184
public function testWaitingForConnectionTimeoutDuringDnsLookupShouldNotCreateAnyGarbageReferences()
188185
{
189186
if (class_exists('React\Promise\When')) {
@@ -217,9 +214,6 @@ function ($e) use (&$wait) {
217214
$this->assertEquals(0, gc_collect_cycles());
218215
}
219216

220-
/**
221-
* @requires PHP 7
222-
*/
223217
public function testWaitingForConnectionTimeoutDuringTcpConnectionShouldNotCreateAnyGarbageReferences()
224218
{
225219
if (class_exists('React\Promise\When')) {

Diff for: tests/TimeoutConnectorTest.php

+62-15
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
namespace React\Tests\Socket;
44

5+
use Clue\React\Block;
56
use React\Socket\TimeoutConnector;
67
use React\Promise;
78
use React\EventLoop\Factory;
9+
use React\Promise\Deferred;
810

911
class TimeoutConnectorTest extends TestCase
1012
{
11-
public function testRejectsOnTimeout()
13+
/**
14+
* @expectedException RuntimeException
15+
* @expectedExceptionMessage Connection to google.com:80 timed out after 0.01 seconds
16+
*/
17+
public function testRejectsWithTimeoutReasonOnTimeout()
1218
{
1319
$promise = new Promise\Promise(function () { });
1420

@@ -19,17 +25,16 @@ public function testRejectsOnTimeout()
1925

2026
$timeout = new TimeoutConnector($connector, 0.01, $loop);
2127

22-
$timeout->connect('google.com:80')->then(
23-
$this->expectCallableNever(),
24-
$this->expectCallableOnce()
25-
);
26-
27-
$loop->run();
28+
Block\await($timeout->connect('google.com:80'), $loop);
2829
}
2930

30-
public function testRejectsWhenConnectorRejects()
31+
/**
32+
* @expectedException RuntimeException
33+
* @expectedExceptionMessage Failed
34+
*/
35+
public function testRejectsWithOriginalReasonWhenConnectorRejects()
3136
{
32-
$promise = Promise\reject(new \RuntimeException());
37+
$promise = Promise\reject(new \RuntimeException('Failed'));
3338

3439
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
3540
$connector->expects($this->once())->method('connect')->with('google.com:80')->will($this->returnValue($promise));
@@ -38,12 +43,7 @@ public function testRejectsWhenConnectorRejects()
3843

3944
$timeout = new TimeoutConnector($connector, 5.0, $loop);
4045

41-
$timeout->connect('google.com:80')->then(
42-
$this->expectCallableNever(),
43-
$this->expectCallableOnce()
44-
);
45-
46-
$loop->run();
46+
Block\await($timeout->connect('google.com:80'), $loop);
4747
}
4848

4949
public function testResolvesWhenConnectorResolves()
@@ -100,4 +100,51 @@ public function testCancelsPendingPromiseOnCancel()
100100

101101
$out->then($this->expectCallableNever(), $this->expectCallableOnce());
102102
}
103+
104+
public function testRejectionDuringConnectionShouldNotCreateAnyGarbageReferences()
105+
{
106+
if (class_exists('React\Promise\When')) {
107+
$this->markTestSkipped('Not supported on legacy Promise v1 API');
108+
}
109+
110+
gc_collect_cycles();
111+
112+
$connection = new Deferred();
113+
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
114+
$connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($connection->promise());
115+
116+
$loop = Factory::create();
117+
$timeout = new TimeoutConnector($connector, 0.01, $loop);
118+
119+
$promise = $timeout->connect('example.com:80');
120+
$connection->reject(new \RuntimeException('Connection failed'));
121+
unset($promise, $connection);
122+
123+
$this->assertEquals(0, gc_collect_cycles());
124+
}
125+
126+
public function testRejectionDueToTimeoutShouldNotCreateAnyGarbageReferences()
127+
{
128+
if (class_exists('React\Promise\When')) {
129+
$this->markTestSkipped('Not supported on legacy Promise v1 API');
130+
}
131+
132+
gc_collect_cycles();
133+
134+
$connection = new Deferred(function () {
135+
throw new \RuntimeException('Connection cancelled');
136+
});
137+
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
138+
$connector->expects($this->once())->method('connect')->with('example.com:80')->willReturn($connection->promise());
139+
140+
$loop = Factory::create();
141+
$timeout = new TimeoutConnector($connector, 0, $loop);
142+
143+
$promise = $timeout->connect('example.com:80');
144+
145+
$loop->run();
146+
unset($promise, $connection);
147+
148+
$this->assertEquals(0, gc_collect_cycles());
149+
}
103150
}

0 commit comments

Comments
 (0)