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

src/TimeoutConnector.php

Lines changed: 26 additions & 1 deletion
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
}

tests/DnsConnectorTest.php

Lines changed: 0 additions & 15 deletions
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')) {

tests/IntegrationTest.php

Lines changed: 0 additions & 6 deletions
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')) {

tests/TimeoutConnectorTest.php

Lines changed: 62 additions & 15 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy