diff --git a/.travis.yml b/.travis.yml index 7182febc..1cd43c2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,20 @@ matrix: - php -r "file_put_contents(php_ini_loaded_file(),'extension=event'.PHP_EOL,FILE_APPEND);" install: - composer install + - name: "Windows PHP 7.4 with ext-uv" + os: windows + language: shell # no built-in php support + before_install: + - curl -OL https://windows.php.net/downloads/pecl/releases/uv/0.2.4/php_uv-0.2.4-7.4-nts-vc15-x64.zip # latest version as of 2019-12-23 + - choco install php --version=7.4.0 # latest version supported by ext-uv as of 2019-12-23 + - choco install composer + - export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')" + - php -r "\$z=new ZipArchive();\$z->open(glob('php_uv*.zip')[0]);\$z->extractTo(dirname(php_ini_loaded_file()).'/ext','php_uv.dll');\$z->extractTo(dirname(php_ini_loaded_file()),'libuv.dll');" + - php -r "file_put_contents(php_ini_loaded_file(),'extension_dir=ext'.PHP_EOL,FILE_APPEND);" + - php -r "file_put_contents(php_ini_loaded_file(),'extension=sockets'.PHP_EOL,FILE_APPEND);" # ext-sockets needs to be loaded before ext-uv + - php -r "file_put_contents(php_ini_loaded_file(),'extension=uv'.PHP_EOL,FILE_APPEND);" + install: + - composer install allow_failures: - php: hhvm - os: windows diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index 70471b59..002d6a2e 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -294,13 +294,15 @@ private function pollStream($stream) private function createStreamListener() { $callback = function ($event, $status, $events, $stream) { - if (!isset($this->streamEvents[(int) $stream])) { - return; - } - - if (($events | 4) === 4) { - // Disconnected - return; + // libuv automatically stops polling on error, re-enable polling to match other loop implementations + if ($status !== 0) { + $this->pollStream($stream); + + // libuv may report no events on error, but this should still invoke stream listeners to report closed connections + // re-enable both readable and writable, correct listeners will be checked below anyway + if ($events === 0) { + $events = \UV::READABLE | \UV::WRITABLE; + } } if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) { diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 2e46b66e..44157d2c 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -2,6 +2,9 @@ namespace React\Tests\EventLoop; +use React\EventLoop\StreamSelectLoop; +use React\EventLoop\ExtUvLoop; + abstract class AbstractLoopTest extends TestCase { /** @@ -36,8 +39,116 @@ public function createSocketPair() return $sockets; } + public function testAddReadStreamTriggersWhenSocketReceivesData() + { + list ($input, $output) = $this->createSocketPair(); + + $loop = $this->loop; + $timeout = $loop->addTimer(0.1, function () use ($input, $loop) { + $loop->removeReadStream($input); + }); + + $called = 0; + $this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) { + ++$called; + $loop->removeReadStream($input); + $loop->cancelTimer($timeout); + }); + + fwrite($output, "foo\n"); + + $this->loop->run(); + + $this->assertEquals(1, $called); + } + + public function testAddReadStreamTriggersWhenSocketCloses() + { + list ($input, $output) = $this->createSocketPair(); + + $loop = $this->loop; + $timeout = $loop->addTimer(0.1, function () use ($input, $loop) { + $loop->removeReadStream($input); + }); + + $called = 0; + $this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) { + ++$called; + $loop->removeReadStream($input); + $loop->cancelTimer($timeout); + }); + + fclose($output); + + $this->loop->run(); + + $this->assertEquals(1, $called); + } + + public function testAddWriteStreamTriggersWhenSocketConnectionSucceeds() + { + $server = stream_socket_server('127.0.0.1:0'); + + $errno = $errstr = null; + $connecting = stream_socket_client(stream_socket_get_name($server, false), $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT); + + $loop = $this->loop; + $timeout = $loop->addTimer(0.1, function () use ($connecting, $loop) { + $loop->removeWriteStream($connecting); + }); + + $called = 0; + $this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) { + ++$called; + $loop->removeWriteStream($connecting); + $loop->cancelTimer($timeout); + }); + + $this->loop->run(); + + $this->assertEquals(1, $called); + } + + public function testAddWriteStreamTriggersWhenSocketConnectionRefused() + { + // @link https://github.com/reactphp/event-loop/issues/206 + if ($this->loop instanceof StreamSelectLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('StreamSelectLoop does not currently support detecting connection refused errors on Windows'); + } + + // first verify the operating system actually refuses the connection and no firewall is in place + // use higher timeout because Windows retires multiple times and has a noticeable delay + // @link https://stackoverflow.com/questions/19440364/why-do-failed-attempts-of-socket-connect-take-1-sec-on-windows + $errno = $errstr = null; + if (@stream_socket_client('127.0.0.1:1', $errno, $errstr, 10.0) !== false || $errno !== SOCKET_ECONNREFUSED) { + $this->markTestSkipped('Expected host to refuse connection, but got error ' . $errno . ': ' . $errstr); + } + + $connecting = stream_socket_client('127.0.0.1:1', $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT); + + $loop = $this->loop; + $timeout = $loop->addTimer(10.0, function () use ($connecting, $loop) { + $loop->removeWriteStream($connecting); + }); + + $called = 0; + $this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) { + ++$called; + $loop->removeWriteStream($connecting); + $loop->cancelTimer($timeout); + }); + + $this->loop->run(); + + $this->assertEquals(1, $called); + } + public function testAddReadStream() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableExactly(2)); @@ -51,6 +162,10 @@ public function testAddReadStream() public function testAddReadStreamIgnoresSecondCallable() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableExactly(2)); @@ -100,6 +215,10 @@ private function subAddReadStreamReceivesDataFromStreamReference() public function testAddWriteStream() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input) = $this->createSocketPair(); $this->loop->addWriteStream($input, $this->expectCallableExactly(2)); @@ -109,6 +228,10 @@ public function testAddWriteStream() public function testAddWriteStreamIgnoresSecondCallable() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input) = $this->createSocketPair(); $this->loop->addWriteStream($input, $this->expectCallableExactly(2)); @@ -119,6 +242,10 @@ public function testAddWriteStreamIgnoresSecondCallable() public function testRemoveReadStreamInstantly() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableNever()); @@ -130,6 +257,10 @@ public function testRemoveReadStreamInstantly() public function testRemoveReadStreamAfterReading() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableOnce()); @@ -145,6 +276,10 @@ public function testRemoveReadStreamAfterReading() public function testRemoveWriteStreamInstantly() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input) = $this->createSocketPair(); $this->loop->addWriteStream($input, $this->expectCallableNever()); @@ -154,6 +289,10 @@ public function testRemoveWriteStreamInstantly() public function testRemoveWriteStreamAfterWriting() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input) = $this->createSocketPair(); $this->loop->addWriteStream($input, $this->expectCallableOnce()); @@ -165,6 +304,10 @@ public function testRemoveWriteStreamAfterWriting() public function testRemoveStreamForReadOnly() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input, $output) = $this->createSocketPair(); $this->loop->addReadStream($input, $this->expectCallableNever()); @@ -177,6 +320,10 @@ public function testRemoveStreamForReadOnly() public function testRemoveStreamForWriteOnly() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($input, $output) = $this->createSocketPair(); fwrite($output, "foo\n"); @@ -399,6 +546,10 @@ public function testFutureTick() public function testFutureTickFiresBeforeIO() { + if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') { + $this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows'); + } + list ($stream) = $this->createSocketPair(); $this->loop->addWriteStream( @@ -419,6 +570,9 @@ function () { $this->tickLoop($this->loop); } + /** + * @depends testFutureTickFiresBeforeIO + */ public function testRecursiveFutureTick() { list ($stream) = $this->createSocketPair(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy