Skip to content

Commit 3f4a3c8

Browse files
committed
Fix cancelling happy eyeballs when IPv6 resolution is pending
1 parent 0cd247c commit 3f4a3c8

File tree

2 files changed

+14
-11
lines changed

2 files changed

+14
-11
lines changed

src/HappyEyeBallsConnectionBuilder.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,8 @@ public function __construct(LoopInterface $loop, ConnectorInterface $connector,
6565

6666
public function connect()
6767
{
68-
$timer = null;
6968
$that = $this;
70-
return new Promise\Promise(function ($resolve, $reject) use ($that, &$timer) {
69+
return new Promise\Promise(function ($resolve, $reject) use ($that) {
7170
$lookupResolve = function ($type) use ($that, $resolve, $reject) {
7271
return function (array $ips) use ($that, $type, $resolve, $reject) {
7372
unset($that->resolverPromises[$type]);
@@ -83,36 +82,36 @@ public function connect()
8382
};
8483

8584
$that->resolverPromises[Message::TYPE_AAAA] = $that->resolve(Message::TYPE_AAAA, $reject)->then($lookupResolve(Message::TYPE_AAAA));
86-
$that->resolverPromises[Message::TYPE_A] = $that->resolve(Message::TYPE_A, $reject)->then(function (array $ips) use ($that, &$timer) {
85+
$that->resolverPromises[Message::TYPE_A] = $that->resolve(Message::TYPE_A, $reject)->then(function (array $ips) use ($that) {
8786
// happy path: IPv6 has resolved already (or could not resolve), continue with IPv4 addresses
8887
if ($that->resolved[Message::TYPE_AAAA] === true || !$ips) {
8988
return $ips;
9089
}
9190

9291
// Otherwise delay processing IPv4 lookup until short timer passes or IPv6 resolves in the meantime
93-
$deferred = new Promise\Deferred();
92+
$deferred = new Promise\Deferred(function () use (&$ips) {
93+
// discard all IPv4 addresses if cancelled
94+
$ips = array();
95+
});
9496
$timer = $that->loop->addTimer($that::RESOLUTION_DELAY, function () use ($deferred, $ips) {
9597
$deferred->resolve($ips);
9698
});
9799

98-
$that->resolverPromises[Message::TYPE_AAAA]->then(function () use ($that, $timer, $deferred, $ips) {
100+
$that->resolverPromises[Message::TYPE_AAAA]->then(function () use ($that, $timer, $deferred, &$ips) {
99101
$that->loop->cancelTimer($timer);
100102
$deferred->resolve($ips);
101103
});
102104

103105
return $deferred->promise();
104106
})->then($lookupResolve(Message::TYPE_A));
105-
}, function ($_, $reject) use ($that, &$timer) {
107+
}, function ($_, $reject) use ($that) {
106108
$reject(new \RuntimeException(
107109
'Connection to ' . $that->uri . ' cancelled' . (!$that->connectionPromises ? ' during DNS lookup' : '') . ' (ECONNABORTED)',
108110
\defined('SOCKET_ECONNABORTED') ? \SOCKET_ECONNABORTED : 103
109111
));
110112
$_ = $reject = null;
111113

112114
$that->cleanUp();
113-
if ($timer instanceof TimerInterface) {
114-
$that->loop->cancelTimer($timer);
115-
}
116115
});
117116
}
118117

@@ -247,13 +246,15 @@ public function cleanUp()
247246
// clear list of outstanding IPs to avoid creating new connections
248247
$this->connectQueue = array();
249248

249+
// cancel pending connection attempts
250250
foreach ($this->connectionPromises as $connectionPromise) {
251251
if ($connectionPromise instanceof PromiseInterface && \method_exists($connectionPromise, 'cancel')) {
252252
$connectionPromise->cancel();
253253
}
254254
}
255255

256-
foreach ($this->resolverPromises as $resolverPromise) {
256+
// cancel pending DNS resolution (cancel IPv4 first in case it is awaiting IPv6 resolution delay)
257+
foreach (\array_reverse($this->resolverPromises) as $resolverPromise) {
257258
if ($resolverPromise instanceof PromiseInterface && \method_exists($resolverPromise, 'cancel')) {
258259
$resolverPromise->cancel();
259260
}

tests/HappyEyeBallsConnectionBuilderTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -695,7 +695,9 @@ public function testCancelConnectWillRejectPromiseAndCancelPendingIpv6LookupAndC
695695
array('reactphp.org', Message::TYPE_AAAA),
696696
array('reactphp.org', Message::TYPE_A)
697697
)->willReturnOnConsecutiveCalls(
698-
new Promise(function () { }, $this->expectCallableOnce()),
698+
new Promise(function () { }, function () {
699+
throw new \RuntimeException('DNS cancelled');
700+
}),
699701
\React\Promise\resolve(array('127.0.0.1'))
700702
);
701703

0 commit comments

Comments
 (0)