Skip to content

Commit 28fac70

Browse files
authored
Merge pull request #247 from clue-labs/round-robin-dns
Use round robin for happy eyeballs DNS responses (load balancing)
2 parents e2b96b2 + 16ff9ad commit 28fac70

3 files changed

+59
-50
lines changed

src/HappyEyeBallsConnectionBuilder.php

+1
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ public function hasBeenResolved()
316316
*/
317317
public function mixIpsIntoConnectQueue(array $ips)
318318
{
319+
\shuffle($ips);
319320
$this->ipsCount += \count($ips);
320321
$connectQueueStash = $this->connectQueue;
321322
$this->connectQueue = array();

tests/HappyEyeBallsConnectionBuilderTest.php

+58-6
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ public function testConnectWillStartConnectingWithAlternatingIPv6AndIPv4WhenReso
302302
$connector->expects($this->exactly(4))->method('connect')->withConsecutive(
303303
array('tcp://[::1]:80?hostname=reactphp.org'),
304304
array('tcp://127.0.0.1:80?hostname=reactphp.org'),
305-
array('tcp://[::2]:80?hostname=reactphp.org'),
306-
array('tcp://127.0.0.2:80?hostname=reactphp.org')
305+
array('tcp://[::1]:80?hostname=reactphp.org'),
306+
array('tcp://127.0.0.1:80?hostname=reactphp.org')
307307
)->willReturnOnConsecutiveCalls(
308308
$deferred->promise(),
309309
$deferred->promise(),
@@ -316,8 +316,8 @@ public function testConnectWillStartConnectingWithAlternatingIPv6AndIPv4WhenReso
316316
array('reactphp.org', Message::TYPE_AAAA),
317317
array('reactphp.org', Message::TYPE_A)
318318
)->willReturnOnConsecutiveCalls(
319-
\React\Promise\resolve(array('::1', '::2')),
320-
\React\Promise\resolve(array('127.0.0.1', '127.0.0.2'))
319+
\React\Promise\resolve(array('::1', '::1')),
320+
\React\Promise\resolve(array('127.0.0.1', '127.0.0.1'))
321321
);
322322

323323
$uri = 'tcp://reactphp.org:80';
@@ -341,7 +341,7 @@ public function testConnectWillStartConnectingWithAttemptTimerWhenOnlyIpv6Resolv
341341
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
342342
$connector->expects($this->exactly(2))->method('connect')->withConsecutive(
343343
array('tcp://[::1]:80?hostname=reactphp.org'),
344-
array('tcp://[::2]:80?hostname=reactphp.org')
344+
array('tcp://[::1]:80?hostname=reactphp.org')
345345
)->willReturnOnConsecutiveCalls(
346346
\React\Promise\reject(new \RuntimeException()),
347347
new Promise(function () { })
@@ -352,7 +352,7 @@ public function testConnectWillStartConnectingWithAttemptTimerWhenOnlyIpv6Resolv
352352
array('reactphp.org', Message::TYPE_AAAA),
353353
array('reactphp.org', Message::TYPE_A)
354354
)->willReturnOnConsecutiveCalls(
355-
\React\Promise\resolve(array('::1', '::2')),
355+
\React\Promise\resolve(array('::1', '::1')),
356356
\React\Promise\reject(new \RuntimeException())
357357
);
358358

@@ -799,4 +799,56 @@ public function testCleanUpCancelsAllPendingConnectionAttemptsWithoutStartingNew
799799

800800
$builder->cleanUp();
801801
}
802+
803+
public function testMixIpsIntoConnectQueueSometimesAssignsInOriginalOrder()
804+
{
805+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
806+
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
807+
$resolver = $this->getMockBuilder('React\Dns\Resolver\ResolverInterface')->getMock();
808+
809+
$uri = 'tcp://reactphp.org:80/path?test=yes#start';
810+
$host = 'reactphp.org';
811+
$parts = parse_url($uri);
812+
813+
for ($i = 0; $i < 100; ++$i) {
814+
$builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts);
815+
$builder->mixIpsIntoConnectQueue(array('::1', '::2'));
816+
817+
$ref = new \ReflectionProperty($builder, 'connectQueue');
818+
$ref->setAccessible(true);
819+
$value = $ref->getValue($builder);
820+
821+
if ($value === array('::1', '::2')) {
822+
break;
823+
}
824+
}
825+
826+
$this->assertEquals(array('::1', '::2'), $value);
827+
}
828+
829+
public function testMixIpsIntoConnectQueueSometimesAssignsInReverseOrder()
830+
{
831+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
832+
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
833+
$resolver = $this->getMockBuilder('React\Dns\Resolver\ResolverInterface')->getMock();
834+
835+
$uri = 'tcp://reactphp.org:80/path?test=yes#start';
836+
$host = 'reactphp.org';
837+
$parts = parse_url($uri);
838+
839+
for ($i = 0; $i < 100; ++$i) {
840+
$builder = new HappyEyeBallsConnectionBuilder($loop, $connector, $resolver, $uri, $host, $parts);
841+
$builder->mixIpsIntoConnectQueue(array('::1', '::2'));
842+
843+
$ref = new \ReflectionProperty($builder, 'connectQueue');
844+
$ref->setAccessible(true);
845+
$value = $ref->getValue($builder);
846+
847+
if ($value === array('::2', '::1')) {
848+
break;
849+
}
850+
}
851+
852+
$this->assertEquals(array('::2', '::1'), $value);
853+
}
802854
}

tests/HappyEyeBallsConnectorTest.php

-44
Original file line numberDiff line numberDiff line change
@@ -270,50 +270,6 @@ public function testCancelDuringTcpConnectionCancelsTcpConnectionIfGivenIp()
270270
$this->loop->run();
271271
}
272272

273-
/**
274-
* @dataProvider provideIpvAddresses
275-
*/
276-
public function testShouldConnectOverIpv4WhenIpv6LookupFails(array $ipv6, array $ipv4)
277-
{
278-
$this->resolver->expects($this->exactly(2))->method('resolveAll')->withConsecutive(
279-
array($this->equalTo('example.com'), Message::TYPE_AAAA),
280-
array($this->equalTo('example.com'), Message::TYPE_A)
281-
)->willReturnOnConsecutiveCalls(
282-
Promise\reject(new \Exception('failure')),
283-
Promise\resolve($ipv4)
284-
);
285-
$this->tcp->expects($this->exactly(1))->method('connect')->with($this->equalTo('1.2.3.4:80?hostname=example.com'))->willReturn(Promise\resolve($this->connection));
286-
287-
$promise = $this->connector->connect('example.com:80');;
288-
$resolvedConnection = Block\await($promise, $this->loop);
289-
290-
self::assertSame($this->connection, $resolvedConnection);
291-
}
292-
293-
/**
294-
* @dataProvider provideIpvAddresses
295-
*/
296-
public function testShouldConnectOverIpv6WhenIpv4LookupFails(array $ipv6, array $ipv4)
297-
{
298-
if (count($ipv6) === 0) {
299-
$ipv6[] = '1:2:3:4';
300-
}
301-
302-
$this->resolver->expects($this->exactly(2))->method('resolveAll')->withConsecutive(
303-
array($this->equalTo('example.com'), Message::TYPE_AAAA),
304-
array($this->equalTo('example.com'), Message::TYPE_A)
305-
)->willReturnOnConsecutiveCalls(
306-
Promise\resolve($ipv6),
307-
Promise\reject(new \Exception('failure'))
308-
);
309-
$this->tcp->expects($this->exactly(1))->method('connect')->with($this->equalTo('[1:2:3:4]:80?hostname=example.com'))->willReturn(Promise\resolve($this->connection));
310-
311-
$promise = $this->connector->connect('example.com:80');;
312-
$resolvedConnection = Block\await($promise, $this->loop);
313-
314-
self::assertSame($this->connection, $resolvedConnection);
315-
}
316-
317273
/**
318274
* @internal
319275
*/

0 commit comments

Comments
 (0)