From b87ea7f0a25e3d67cd76ba0a7109740fcc4f7c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 4 Apr 2022 09:31:58 +0200 Subject: [PATCH] Fix reporting refused connections on Windows --- src/Socket.php | 12 ++++++++++-- tests/SocketTest.php | 16 +++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Socket.php b/src/Socket.php index 42a4570..67407f2 100644 --- a/src/Socket.php +++ b/src/Socket.php @@ -152,8 +152,16 @@ public function connectTimeout($address, $timeout) throw $e; } - // connection should be completed (or rejected) within timeout - if ($this->selectWrite($timeout) === false) { + // connection should be completed (or rejected) within timeout: socket becomes writable on success or error + // Windows requires special care because it uses exceptfds for socket errors: https://github.com/reactphp/event-loop/issues/206 + $r = null; + $w = array($this->resource); + $e = DIRECTORY_SEPARATOR === '\\' ? $w : null; + $ret = @socket_select($r, $w, $e, $timeout === null ? null : (int) $timeout, (int) (($timeout - floor($timeout)) * 1000000)); + + if ($ret === false) { + throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing'); + } elseif ($ret === 0) { throw new Exception('Timed out while waiting for connection', SOCKET_ETIMEDOUT); } diff --git a/tests/SocketTest.php b/tests/SocketTest.php index 52313f4..3e3ae9d 100644 --- a/tests/SocketTest.php +++ b/tests/SocketTest.php @@ -107,8 +107,13 @@ public function testConnectAsyncGoogle() public function testConnectAsyncFailUnbound() { - if (PHP_OS !== 'Linux') { - $this->markTestSkipped('Only Linux is known to refuse connections to unbound addresses'); + $client = @stream_socket_client('localhost:2', $errno, $errstr, 5.0); + if ($client !== false || $errno !== SOCKET_ECONNREFUSED) { + $this->markTestSkipped('Expected unbound address to return ECONNREFUSED, but got errno ' . $errno); + } + + if (DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Windows uses EWOULDBLOCK and exceptfds, see connectTimeout() instead'); } $socket = $this->factory->createTcp4(); @@ -187,15 +192,16 @@ public function testConnectTimeoutFailTimeout() public function testConnectTimeoutFailUnbound() { - if (PHP_OS !== 'Linux') { - $this->markTestSkipped('Only Linux is known to refuse connections to unbound addresses'); + $client = @stream_socket_client('localhost:2', $errno, $errstr, 5.0); + if ($client !== false || $errno !== SOCKET_ECONNREFUSED) { + $this->markTestSkipped('Expected unbound address to return ECONNREFUSED, but got errno ' . $errno); } $socket = $this->factory->createTcp4(); $this->setExpectedException('Socket\Raw\Exception', null, SOCKET_ECONNREFUSED); - $socket->connectTimeout('localhost:2', 0.5); + $socket->connectTimeout('localhost:2', 5.0); } /** @group internet */