8000 Merge pull request #207 from clue-labs/ext-uv-connections · reactphp/event-loop@e79f422 · GitHub
[go: up one dir, main page]

Skip to content

Commit e79f422

Browse files
authored
Merge pull request #207 from clue-labs/ext-uv-connections
ExtUvLoop: Fix reporting connection refused errors
2 parents 1d8055f + 6d45a19 commit e79f422

File tree

3 files changed

+177
-7
lines changed

3 files changed

+177
-7
lines changed

.travis.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ matrix:
5151
- php -r "file_put_contents(php_ini_loaded_file(),'extension=event'.PHP_EOL,FILE_APPEND);"
5252
install:
5353
- composer install
54+
- name: "Windows PHP 7.4 with ext-uv"
55+
os: windows
56+
language: shell # no built-in php support
57+
before_install:
58+
- 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
59+
- choco install php --version=7.4.0 # latest version supported by ext-uv as of 2019-12-23
60+
- choco install composer
61+
- export PATH="$(powershell -Command '("Process", "Machine" | % { [Environment]::GetEnvironmentVariable("PATH", $_) -Split ";" -Replace "\\$", "" } | Select -Unique | % { cygpath $_ }) -Join ":"')"
62+
- 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');"
63+
- php -r "file_put_contents(php_ini_loaded_file(),'extension_dir=ext'.PHP_EOL,FILE_APPEND);"
64+
- php -r "file_put_contents(php_ini_loaded_file(),'extension=sockets'.PHP_EOL,FILE_APPEND);" # ext-sockets needs to be loaded before ext-uv
65+
- php -r "file_put_contents(php_ini_loaded_file(),'extension=uv'.PHP_EOL,FILE_APPEND);"
66+
install:
67+
- composer install
5468
allow_failures:
5569
- php: hhvm
5670
- os: windows

src/ExtUvLoop.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -294,13 +294,15 @@ private function pollStream($stream)
294294
private function createStreamListener()
295295
{
296296
$callback = function ($event, $status, $events, $stream) {
297-
if (!isset($this->streamEvents[(int) $stream])) {
298-
return;
299-
}
300-
301-
if (($events | 4) === 4) {
302-
// Disconnected
303-
return;
297+
// libuv automatically stops polling on error, re-enable polling to match other loop implementations
298+
if ($status !== 0) {
299+
$this->pollStream($stream);
300+
301+
// libuv may report no events on error, but this should still invoke stream listeners to report closed connections
302+
// re-enable both readable and writable, correct listeners will be checked below anyway
303+
if ($events === 0) {
304+
$events = \UV::READABLE | \UV::WRITABLE;
305+
}
304306
}
305307

306308
if (isset($this->readStreams[(int) $stream]) && ($events & \UV::READABLE)) {

tests/AbstractLoopTest.php

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace React\Tests\EventLoop;
44

5+
use React\EventLoop\StreamSelectLoop;
6+
use React\EventLoop\ExtUvLoop;
7+
58
abstract class AbstractLoopTest extends TestCase
69
{
710
/**
@@ -36,8 +39,116 @@ public function createSocketPair()
3639
return $sockets;
3740
}
3841

42+
public function testAddReadStreamTriggersWhenSocketReceivesData()
43+
{
44+
list ($input, $output) = $this->createSocketPair();
45+
46+
$loop = $this->loop;
47+
$timeout = $loop->addTimer(0.1, function () use ($input, $loop) {
48+
$loop->removeReadStream($input);
49+
});
50+
51+
$called = 0;
52+
$this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) {
53+
++$called;
54+
$loop->removeReadStream($input);
55+
$loop->cancelTimer($timeout);
56+
});
57+
58+
fwrite($output, "foo\n");
59+
60+
$this->loop->run();
61+
62+
$this->assertEquals(1, $called);
63+
}
64+
65+
public function testAddReadStreamTriggersWhenSocketCloses()
66+
{
67+
list ($input, $output) = $this->createSocketPair();
68+
69+
$loop = $this->loop;
70+
$timeout = $loop->addTimer(0.1, function () use ($input, $loop) {
71+
$loop->removeReadStream($input);
72+
});
73+
74+
$called = 0;
75+
$this->loop->addReadStream($input, function () use (&$called, $loop, $input, $timeout) {
76+
++$called;
77+
$loop->removeReadStream($input);
78+
$loop->cancelTimer($timeout);
79+
});
80+
81+
fclose($output);
82+
83+
$this->loop->run();
84+
85+
$this->assertEquals(1, $called);
86+
}
87+
88+
public function testAddWriteStreamTriggersWhenSocketConnectionSucceeds()
89+
{
90+
$server = stream_socket_server('127.0.0.1:0');
91+
92+
$errno = $errstr = null;
93+
$connecting = stream_socket_client(stream_socket_get_name($server, false), $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
94+
95+
$loop = $this->loop;
96+
$timeout = $loop->addTimer(0.1, function () use ($connecting, $loop) {
97+
$loop->removeWriteStream($connecting);
98+
});
99+
100+
$called = 0;
101+
$this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) {
102+
++$called;
103+
$loop->removeWriteStream($connecting);
104+
$loop->cancelTimer($timeout);
105+
});
106+
107+
$this->loop->run();
108+
109+
$this->assertEquals(1, $called);
110+
}
111+
112+
public function testAddWriteStreamTriggersWhenSocketConnectionRefused()
113+
{
114+
// @link https://github.com/reactphp/event-loop/issues/206
115+
if ($this->loop instanceof StreamSelectLoop && DIRECTORY_SEPARATOR === '\\') {
116+
$this->markTestIncomplete('StreamSelectLoop does not currently support detecting connection refused errors on Windows');
117+
}
118+
119+
// first verify the operating system actually refuses the connection and no firewall is in place
120+
// use higher timeout because Windows retires multiple times and has a noticeable delay
121+
// @link https://stackoverflow.com/questions/19440364/why-do-failed-attempts-of-socket-connect-take-1-sec-on-windows
122+
$errno = $errstr = null;
123+
if (@stream_socket_client('127.0.0.1:1', $errno, $errstr, 10.0) !== false || $errno !== SOCKET_ECONNREFUSED) {
124+
$this->markTestSkipped('Expected host to refuse connection, but got error ' . $errno . ': ' . $errstr);
125+
}
126+
127+
$connecting = stream_socket_client('127.0.0.1:1', $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
128+
129+
$loop = $this->loop;
130+
$timeout = $loop->addTimer(10.0, function () use ($connecting, $loop) {
131+
$loop->removeWriteStream($connecting);
132+
});
133+
134+
$called = 0;
135+
$this->loop->addWriteStream($connecting, function () use (&$called, $loop, $connecting, $timeout) {
136+
++$called;
137+
$loop->removeWriteStream($connecting);
138+
$loop->cancelTimer($timeout);
139+
});
140+
141+
$this->loop->run();
142+
143+
$this->assertEquals(1, $called);
144+
}
145+
39146
public function testAddReadStream()
40147
{
148+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
149+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
150+
}
151+
41152
list ($input, $output) = $this->createSocketPair();
42153

43154
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
@@ -51,6 +162,10 @@ public function testAddReadStream()
51162

52163
public function testAddReadStreamIgnoresSecondCallable()
53164
{
165+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
166+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
167+
}
168+
54169
list ($input, $output) = $this->createSocketPair();
55170

56171
$this->loop->addReadStream($input, $this->expectCallableExactly(2));
@@ -100,6 +215,10 @@ private function subAddReadStreamReceivesDataFromStreamReference()
100215

101216
public function testAddWriteStream()
102217
{
218+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
219+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
220+
}
221+
103222< F438 /code>
list ($input) = $this->createSocketPair();
104223

105224
$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
@@ -109,6 +228,10 @@ public function testAddWriteStream()
109228

110229
public function testAddWriteStreamIgnoresSecondCallable()
111230
{
231+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
232+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
233+
}
234+
112235
list ($input) = $this->createSocketPair();
113236

114237
$this->loop->addWriteStream($input, $this->expectCallableExactly(2));
@@ -119,6 +242,10 @@ public function testAddWriteStreamIgnoresSecondCallable()
119242

120243
public function testRemoveReadStreamInstantly()
121244
{
245+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
246+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
247+
}
248+
122249
list ($input, $output) = $this->createSocketPair();
123250

124251
$this->loop->addReadStream($input, $this->expectCallableNever());
@@ -130,6 +257,10 @@ public function testRemoveReadStreamInstantly()
130257

131258
public function testRemoveReadStreamAfterReading()
132259
{
260+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
261+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
262+
}
263+
133264
list ($input, $output) = $this->createSocketPair();
134265

135266
$this->loop->addReadStream($input, $this->expectCallableOnce());
@@ -145,6 +276,10 @@ public function testRemoveReadStreamAfterReading()
145276

146277
public function testRemoveWriteStreamInstantly()
147278
{
279+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
280+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
281+
}
282+
148283
list ($input) = $this->createSocketPair();
149284

150285
$this->loop->addWriteStream($input, $this->expectCallableNever());
@@ -154,6 +289,10 @@ public function testRemoveWriteStreamInstantly()
154289

155290
public function testRemoveWriteStreamAfterWriting()
156291
{
292+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
293+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
294+
}
295+
157296
list ($input) = $this->createSocketPair();
158297

159298
$this->loop->addWriteStream($input, $this->expectCallableOnce());
@@ -165,6 +304,10 @@ public function testRemoveWriteStreamAfterWriting()
165304

166305
public function testRemoveStreamForReadOnly()
167306
{
307+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
308+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
309+
}
310+
168311
list ($input, $output) = $this->createSocketPair();
169312

170313
$this->loop->addReadStream($input, $this->expectCallableNever());
@@ -177,6 +320,10 @@ public function testRemoveStreamForReadOnly()
177320

178321
public function testRemoveStreamForWriteOnly()
179322
{
323+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
324+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
325+
}
326+
180327
list ($input, $output) = $this->createSocketPair();
181328

182329
fwrite($output, "foo\n");
@@ -399,6 +546,10 @@ public function testFutureTick()
399546

400547
public function testFutureTickFiresBeforeIO()
401548
{
549+
if ($this->loop instanceof ExtUvLoop && DIRECTORY_SEPARATOR === '\\') {
550+
$this->markTestIncomplete('Ticking ExtUvLoop not supported on Windows');
551+
}
552+
402553
list ($stream) = $this->createSocketPair();
403554

404555
$this->loop->addWriteStream(
@@ -419,6 +570,9 @@ function () {
419570
$this->tickLoop($this->loop);
420571
}
421572

573+
/**
574+
* @depends testFutureTickFiresBeforeIO
575+
*/
422576
public function testRecursiveFutureTick()
423577
{
424578
list ($stream) = $this->createSocketPair();

0 commit comments

Comments
 (0)
0