Skip to content

Commit

Permalink
Implement except handling on Windows (#341)
Browse files Browse the repository at this point in the history
  • Loading branch information
kelunik authored Jan 13, 2021
1 parent 973878a commit 7d4bbc6
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ jobs:
runs-on: ${{ matrix.operating-system }}

steps:
- name: Set git to use LF
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout code
uses: actions/checkout@v2

Expand Down
12 changes: 12 additions & 0 deletions lib/Loop/NativeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,13 @@ private function selectStreams(array $read, array $write, int $timeout)
$microseconds = null;
}

// Failed connection attempts are indicated via except on Windows
// @link https://github.com/reactphp/event-loop/blob/8bd064ce23c26c4decf186c2a5a818c9a8209eb0/src/StreamSelectLoop.php#L279-L287
// @link https://docs.microsoft.com/de-de/windows/win32/api/winsock2/nf-winsock2-select
$except = null;
if (\DIRECTORY_SEPARATOR === '\\') {
$except = $write;
}

\set_error_handler($this->streamSelectErrorHandler);

Expand Down Expand Up @@ -339,6 +345,12 @@ private function selectStreams(array $read, array $write, int $timeout)

\assert(\is_array($write)); // See https://github.com/vimeo/psalm/issues/3036

if ($except) {
foreach ($except as $key => $socket) {
$write[$key] = $socket;
}
}

foreach ($write as $stream) {
$streamId = (int) $stream;
if (!isset($this->writeWatchers[$streamId])) {
Expand Down
72 changes: 70 additions & 2 deletions test/Loop/DriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public function testCorrectTimeoutIfBlockingBeforeDelay(): void
self::assertNotSame(0, $start);
self::assertNotSame(0, $invoked);

self::assertGreaterThanOrEqual(1500, $invoked - $start);
self::assertGreaterThanOrEqual(1499, $invoked - $start);
self::assertLessThan(1750, $invoked - $start);
}

Expand Down Expand Up @@ -586,6 +586,10 @@ public function testNoMemoryLeak($type, $args)
self::markTestSkipped("Cannot run this test with code coverage active [code coverage consumes memory which makes it impossible to rely on memory_get_usage()]");
}

if (\DIRECTORY_SEPARATOR === '\\') {
self::markTestSkipped('Skip on Windows for now, investigate');
}

$runs = 2000;

if ($type === "onSignal") {
Expand Down Expand Up @@ -1610,13 +1614,77 @@ public function testMultipleWatchersOnSameDescriptor(): void
self::assertSame(2323, $invoked);
}

/**
* This test case is based on React's tests.
*
* The MIT License (MIT)
*
* Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished
* to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @link https://github.com/reactphp/event-loop/blob/8bd064ce23c26c4decf186c2a5a818c9a8209eb0/tests/AbstractLoopTest.php#L115-L146
*/
public function testStreamWritableIfConnectFails(): void
{
// 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) !== false
|| (\defined('SOCKET_ECONNREFUSED') && $errno !== \SOCKET_ECONNREFUSED)
) {
self::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
);

$called = 0;
$writeWatcher = $this->loop->onWritable($connecting, function (string $watcher) use (&$called) {
++$called;

$this->loop->cancel($watcher);
});

$this->loop->unreference($this->loop->delay(10000, function () use ($writeWatcher) {
$this->loop->cancel($writeWatcher);
}));

$this->loop->run();

self::assertEquals(1, $called);
}

public function testTimerIntervalCountedWhenNotRunning(): void
{
$this->loop->delay(1000, function () use (&$start) {
$this->assertLessThan(0.5, \microtime(true) - $start);
});

\usleep(600000); // 600ms instead of 500ms to allow for variations in timing.
\usleep(750000); // 600ms instead of 500ms to allow for variations in timing.
$start = \microtime(true);
$this->loop->run();
}
Expand Down
18 changes: 15 additions & 3 deletions test/Loop/NativeDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ public function getFactory(): callable

public function testHandle()
{
$this->assertNull($this->loop->getHandle());
self::assertNull($this->loop->getHandle());
}

public function testTooLargeFileDescriptorSet()
{
if (\DIRECTORY_SEPARATOR === '\\') {
self::markTestSkipped('Skipped on Windows');
}

$sockets = [];
$domain = \stripos(PHP_OS, 'win') === 0 ? STREAM_PF_INET : STREAM_PF_UNIX;

Expand All @@ -36,7 +40,7 @@ public function testTooLargeFileDescriptorSet()
// here to provide timeout to stream_select, as the warning is only issued after the system call returns
});

foreach ($sockets as list($left, $right)) {
foreach ($sockets as [$left, $right]) {
$loop->onReadable($left, function () {
// nothing
});
Expand All @@ -50,6 +54,10 @@ public function testTooLargeFileDescriptorSet()

public function testSignalDuringStreamSelectIgnored()
{
if (\DIRECTORY_SEPARATOR === '\\') {
self::markTestSkipped('Skipped on Windows');
}

$domain = \stripos(PHP_OS, 'win') === 0 ? STREAM_PF_INET : STREAM_PF_UNIX;
$sockets = \stream_socket_pair($domain, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);

Expand Down Expand Up @@ -84,6 +92,10 @@ public function testSignalDuringStreamSelectIgnored()
*/
public function testAsyncSignals()
{
if (\DIRECTORY_SEPARATOR === '\\') {
self::markTestSkipped('Skipped on Windows');
}

\pcntl_async_signals(true);

try {
Expand All @@ -100,6 +112,6 @@ public function testAsyncSignals()
\pcntl_async_signals(false);
}

$this->assertTrue($invoked);
self::assertTrue($invoked);
}
}
6 changes: 3 additions & 3 deletions test/LoopTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ public function testNow()
{
Loop::run(function () {
$now = Loop::now();
Loop::delay(100, function () use ($now) {
$now += 100;
Loop::delay(500, function () use ($now) {
$now += 500;
$new = Loop::now();

// Allow a few milliseconds of inaccuracy.
$this->assertGreaterThanOrEqual($now - 1, $new);
$this->assertLessThanOrEqual($now + 100, $new);
$this->assertLessThanOrEqual($now + 250, $new);
});
});
}
Expand Down
4 changes: 4 additions & 0 deletions test/PsalmTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class PsalmTest extends TestCase
*/
public function test()
{
if (\DIRECTORY_SEPARATOR === '\\') {
self::markTestSkipped('Skipped on Windows');
}

$issues = \json_decode(
\shell_exec('./vendor/bin/psalm.phar --output-format=json --no-progress --config=psalm.examples.xml'),
true
Expand Down

0 comments on commit 7d4bbc6

Please sign in to comment.