diff --git a/src/SocketServer.php b/src/SocketServer.php index 973bbaf8..88ae6487 100644 --- a/src/SocketServer.php +++ b/src/SocketServer.php @@ -90,4 +90,44 @@ public function close() { $this->server->close(); } + + /** + * [internal] Internal helper method to accept new connection from given server socket + * + * @param resource $socket server socket to accept connection from + * @return resource new client socket if any + * @throws \RuntimeException if accepting fails + * @internal + */ + public static function accept($socket) + { + $newSocket = @\stream_socket_accept($socket, 0); + + if (false === $newSocket) { + // Match errstr from PHP's warning message. + // stream_socket_accept(): accept failed: Connection timed out + $error = \error_get_last(); + $errstr = \preg_replace('#.*: #', '', $error['message']); + + // Go through list of possible error constants to find matching errno. + // @codeCoverageIgnoreStart + $errno = 0; + if (\function_exists('socket_strerror')) { + foreach (\get_defined_constants(false) as $name => $value) { + if (\strpos($name, 'SOCKET_E') === 0 && \socket_strerror($value) === $errstr) { + $errno = $value; + break; + } + } + } + // @codeCoverageIgnoreEnd + + throw new \RuntimeException( + 'Unable to accept new connection: ' . $errstr, + $errno + ); + } + + return $newSocket; + } } diff --git a/src/TcpServer.php b/src/TcpServer.php index 26eda8f7..622d5575 100644 --- a/src/TcpServer.php +++ b/src/TcpServer.php @@ -211,10 +211,10 @@ public function resume() $that = $this; $this->loop->addReadStream($this->master, function ($master) use ($that) { - $newSocket = @\stream_socket_accept($master, 0); - if (false === $newSocket) { - $that->emit('error', array(new \RuntimeException('Error accepting new connection'))); - + try { + $newSocket = SocketServer::accept($master); + } catch (\RuntimeException $e) { + $that->emit('error', array($e)); return; } $that->handleConnection($newSocket); diff --git a/src/UnixServer.php b/src/UnixServer.php index a3dd8a1a..25accbe0 100644 --- a/src/UnixServer.php +++ b/src/UnixServer.php @@ -113,10 +113,10 @@ public function resume() $that = $this; $this->loop->addReadStream($this->master, function ($master) use ($that) { - $newSocket = @\stream_socket_accept($master, 0); - if (false === $newSocket) { - $that->emit('error', array(new \RuntimeException('Error accepting new connection'))); - + try { + $newSocket = SocketServer::accept($master); + } catch (\RuntimeException $e) { + $that->emit('error', array($e)); return; } $that->handleConnection($newSocket); diff --git a/tests/TcpServerTest.php b/tests/TcpServerTest.php index 564614ca..97bcb241 100644 --- a/tests/TcpServerTest.php +++ b/tests/TcpServerTest.php @@ -287,7 +287,10 @@ public function testEmitsErrorWhenAcceptListenerFails() $server = new TcpServer(0, $loop); - $server->on('error', $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException'))); + $exception = null; + $server->on('error', function ($e) use (&$exception) { + $exception = $e; + }); $this->assertNotNull($listener); $socket = stream_socket_server('tcp://127.0.0.1:0'); @@ -297,6 +300,23 @@ public function testEmitsErrorWhenAcceptListenerFails() $time = microtime(true) - $time; $this->assertLessThan(1, $time); + + $this->assertInstanceOf('RuntimeException', $exception); + assert($exception instanceof \RuntimeException); + $this->assertStringStartsWith('Unable to accept new connection: ', $exception->getMessage()); + + return $exception; + } + + /** + * @param \RuntimeException $e + * @requires extension sockets + * @depends testEmitsErrorWhenAcceptListenerFails + */ + public function testEmitsTimeoutErrorWhenAcceptListenerFails(\RuntimeException $exception) + { + $this->assertEquals('Unable to accept new connection: ' . socket_strerror(SOCKET_ETIMEDOUT), $exception->getMessage()); + $this->assertEquals(SOCKET_ETIMEDOUT, $exception->getCode()); } public function testListenOnBusyPortThrows() diff --git a/tests/UnixServerTest.php b/tests/UnixServerTest.php index 2e240933..0863be13 100644 --- a/tests/UnixServerTest.php +++ b/tests/UnixServerTest.php @@ -292,7 +292,10 @@ public function testEmitsErrorWhenAcceptListenerFails() $server = new UnixServer($this->getRandomSocketUri(), $loop); - $server->on('error', $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException'))); + $exception = null; + $server->on('error', function ($e) use (&$exception) { + $exception = $e; + }); $this->assertNotNull($listener); $socket = stream_socket_server('tcp://127.0.0.1:0'); @@ -302,6 +305,23 @@ public function testEmitsErrorWhenAcceptListenerFails() $time = microtime(true) - $time; $this->assertLessThan(1, $time); + + $this->assertInstanceOf('RuntimeException', $exception); + assert($exception instanceof \RuntimeException); + $this->assertStringStartsWith('Unable to accept new connection: ', $exception->getMessage()); + + return $exception; + } + + /** + * @param \RuntimeException $e + * @requires extension sockets + * @depends testEmitsErrorWhenAcceptListenerFails + */ + public function testEmitsTimeoutErrorWhenAcceptListenerFails(\RuntimeException $exception) + { + $this->assertEquals('Unable to accept new connection: ' . socket_strerror(SOCKET_ETIMEDOUT), $exception->getMessage()); + $this->assertEquals(SOCKET_ETIMEDOUT, $exception->getCode()); } public function testListenOnBusyPortThrows()