diff --git a/composer.json b/composer.json index e78f6ab..d781987 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "evenement/evenement": "~1.0|~2.0", "react/event-loop": ">=0.2, <0.5", "react/dns": ">=0.2, <0.5", - "react/promise": "~2.0|~1.1" + "react/promise": "~2.1|~1.2" }, "require-dev": { "clue/block-react": "~1.0" diff --git a/src/Factory.php b/src/Factory.php index 88f45f7..2ec4d79 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -7,6 +7,7 @@ use React\Promise; use React\Datagram\Socket; use \Exception; +use React\Promise\CancellablePromiseInterface; class Factory { @@ -108,6 +109,23 @@ protected function resolveHost($host) return Promise\reject(new Exception('No resolver given in order to get IP address for given hostname')); } - return $this->resolver->resolve($host); + $promise = $this->resolver->resolve($host); + + // wrap DNS lookup in order to control cancellation behavior + return new Promise\Promise( + function ($resolve, $reject) use ($promise) { + // forward promise resolution + $promise->then($resolve, $reject); + }, + function ($_, $reject) use ($promise) { + // reject with custom message once cancelled + $reject(new \RuntimeException('Cancelled creating socket during DNS lookup')); + + // (try to) cancel pending DNS lookup, otherwise ignoring its results + if ($promise instanceof CancellablePromiseInterface) { + $promise->cancel(); + } + } + ); } } diff --git a/tests/FactoryTest.php b/tests/FactoryTest.php index f843db9..49edade 100644 --- a/tests/FactoryTest.php +++ b/tests/FactoryTest.php @@ -119,4 +119,28 @@ public function testCreateServerWithInvalidHostnameWillReject() { Block\await($this->factory->createServer('/////'), $this->loop); } + + public function testCancelCreateClientWithCancellableHostnameResolver() + { + $promise = new Promise\Promise(function () { }, $this->expectCallableOnce()); + $this->resolver->expects($this->once())->method('resolve')->with('example.com')->willReturn($promise); + + $promise = $this->factory->createClient('example.com:0'); + $promise->cancel(); + + $this->setExpectedException('RuntimeException'); + Block\await($promise, $this->loop); + } + + public function testCancelCreateClientWithUncancellableHostnameResolver() + { + $promise = $this->getMock('React\Promise\PromiseInterface'); + $this->resolver->expects($this->once())->method('resolve')->with('example.com')->willReturn($promise); + + $promise = $this->factory->createClient('example.com:0'); + $promise->cancel(); + + $this->setExpectedException('RuntimeException'); + Block\await($promise, $this->loop); + } }