Skip to content

Commit 69a7b51

Browse files
authored
Merge pull request #99 from true-async/feof-bug
* Fix feof() on sockets: reliable liveness check on Windows
2 parents d22b12e + 9141864 commit 69a7b51

7 files changed

Lines changed: 346 additions & 0 deletions
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
--TEST--
2+
feof() returns false on a live connected socket
3+
--FILE--
4+
<?php
5+
6+
use Async\Channel;
7+
use function Async\spawn;
8+
use function Async\await_all;
9+
10+
echo "Start\n";
11+
12+
$s2c = new Channel(1); // server -> client
13+
$c2s = new Channel(1); // client -> server
14+
15+
$server = spawn(function() use ($s2c, $c2s) {
16+
$socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
17+
if (!$socket) {
18+
echo "Server: failed - $errstr\n";
19+
return;
20+
}
21+
22+
// Signal the address to the client
23+
$s2c->send(stream_socket_get_name($socket, false));
24+
25+
$client = stream_socket_accept($socket, 5);
26+
echo "Server: accepted\n";
27+
28+
// Signal that connection is established
29+
$s2c->send("connected");
30+
31+
// Wait for client to finish feof check
32+
$c2s->recv();
33+
34+
// Close client connection
35+
fclose($client);
36+
echo "Server: closed client\n";
37+
38+
// Signal that server closed the connection
39+
$s2c->send("closed");
40+
41+
fclose($socket);
42+
});
43+
44+
$client = spawn(function() use ($s2c, $c2s) {
45+
$address = $s2c->recv();
46+
47+
$sock = stream_socket_client("tcp://$address", $errno, $errstr, 5);
48+
if (!$sock) {
49+
echo "Client: failed - $errstr\n";
50+
return;
51+
}
52+
echo "Client: connected\n";
53+
54+
// Wait for server to confirm accept
55+
$s2c->recv();
56+
57+
// Socket is alive — feof() MUST return false
58+
$eof = feof($sock);
59+
echo "feof on live socket: " . ($eof ? "true (BUG!)" : "false") . "\n";
60+
61+
// Tell server it can close now
62+
$c2s->send("done");
63+
64+
// Wait for server to confirm close
65+
$s2c->recv();
66+
67+
// Give TCP stack time to deliver FIN
68+
\Async\delay(1);
69+
70+
// Server closed — feof() should return true
71+
$eof = feof($sock);
72+
echo "feof after remote close: " . ($eof ? "true" : "false (BUG!)") . "\n";
73+
74+
fclose($sock);
75+
});
76+
77+
await_all([$server, $client]);
78+
79+
echo "End\n";
80+
81+
?>
82+
--EXPECTF--
83+
Start
84+
%AServer: accepted
85+
%AClient: connected
86+
%Afeof on live socket: false
87+
Server: closed client
88+
feof after remote close: true
89+
End
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
feof() returns false immediately after connect
3+
--FILE--
4+
<?php
5+
6+
use Async\Channel;
7+
use function Async\spawn;
8+
use function Async\await_all;
9+
10+
$s2c = new Channel(1); // server to client
11+
$c2s = new Channel(1); // client to server
12+
13+
$server = spawn(function() use ($s2c, $c2s) {
14+
$socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
15+
$s2c->send(stream_socket_get_name($socket, false));
16+
$client = stream_socket_accept($socket, 5);
17+
$s2c->send("accepted");
18+
$c2s->recv();
19+
fclose($client);
20+
fclose($socket);
21+
});
22+
23+
$client = spawn(function() use ($s2c, $c2s) {
24+
$address = $s2c->recv();
25+
$sock = stream_socket_client("tcp://$address", $errno, $errstr, 5);
26+
$s2c->recv();
27+
28+
echo "feof after connect: " . (feof($sock) ? "true (BUG!)" : "false") . "\n";
29+
30+
$c2s->send("done");
31+
fclose($sock);
32+
});
33+
34+
await_all([$server, $client]);
35+
36+
?>
37+
--EXPECT--
38+
feof after connect: false
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
feof() returns false after sending data
3+
--FILE--
4+
<?php
5+
6+
use Async\Channel;
7+
use function Async\spawn;
8+
use function Async\await_all;
9+
10+
$s2c = new Channel(1);
11+
$c2s = new Channel(1);
12+
13+
$server = spawn(function() use ($s2c, $c2s) {
14+
$socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
15+
$s2c->send(stream_socket_get_name($socket, false));
16+
$client = stream_socket_accept($socket, 5);
17+
$s2c->send("accepted");
18+
$c2s->recv();
19+
fclose($client);
20+
fclose($socket);
21+
});
22+
23+
$client = spawn(function() use ($s2c, $c2s) {
24+
$address = $s2c->recv();
25+
$sock = stream_socket_client("tcp://$address", $errno, $errstr, 5);
26+
$s2c->recv();
27+
28+
fwrite($sock, "hello");
29+
30+
echo "feof after send: " . (feof($sock) ? "true (BUG!)" : "false") . "\n";
31+
32+
$c2s->send("done");
33+
fclose($sock);
34+
});
35+
36+
await_all([$server, $client]);
37+
38+
?>
39+
--EXPECT--
40+
feof after send: false
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
feof() returns false after receiving data
3+
--FILE--
4+
<?php
5+
6+
use Async\Channel;
7+
use function Async\spawn;
8+
use function Async\await_all;
9+
10+
$s2c = new Channel(1);
11+
$c2s = new Channel(1);
12+
13+
$server = spawn(function() use ($s2c, $c2s) {
14+
$socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
15+
$s2c->send(stream_socket_get_name($socket, false));
16+
$client = stream_socket_accept($socket, 5);
17+
fwrite($client, "hello from server");
18+
$s2c->send("sent");
19+
$c2s->recv();
20+
fclose($client);
21+
fclose($socket);
22+
});
23+
24+
$client = spawn(function() use ($s2c, $c2s) {
25+
$address = $s2c->recv();
26+
$sock = stream_socket_client("tcp://$address", $errno, $errstr, 5);
27+
$s2c->recv();
28+
29+
$data = fread($sock, 1024);
30+
echo "received: $data\n";
31+
echo "feof after recv: " . (feof($sock) ? "true (BUG!)" : "false") . "\n";
32+
33+
$c2s->send("done");
34+
fclose($sock);
35+
});
36+
37+
await_all([$server, $client]);
38+
39+
?>
40+
--EXPECT--
41+
received: hello from server
42+
feof after recv: false
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--TEST--
2+
feof() returns true after remote close
3+
--FILE--
4+
<?php
5+
6+
use Async\Channel;
7+
use function Async\spawn;
8+
use function Async\await_all;
9+
10+
$s2c = new Channel(1);
11+
$c2s = new Channel(1);
12+
13+
$server = spawn(function() use ($s2c, $c2s) {
14+
$socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
15+
$s2c->send(stream_socket_get_name($socket, false));
16+
$client = stream_socket_accept($socket, 5);
17+
$s2c->send("accepted");
18+
$c2s->recv();
19+
fclose($client);
20+
$s2c->send("closed");
21+
fclose($socket);
22+
});
23+
24+
$client = spawn(function() use ($s2c, $c2s) {
25+
$address = $s2c->recv();
26+
$sock = stream_socket_client("tcp://$address", $errno, $errstr, 5);
27+
$s2c->recv();
28+
29+
$c2s->send("close now");
30+
$s2c->recv();
31+
32+
\Async\delay(1);
33+
34+
echo "feof after remote close: " . (feof($sock) ? "true" : "false (BUG!)") . "\n";
35+
36+
fclose($sock);
37+
});
38+
39+
await_all([$server, $client]);
40+
41+
?>
42+
--EXPECT--
43+
feof after remote close: true
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
feof() after send then remote close
3+
--FILE--
4+
<?php
5+
6+
use Async\Channel;
7+
use function Async\spawn;
8+
use function Async\await_all;
9+
10+
$s2c = new Channel(1);
11+
$c2s = new Channel(1);
12+
13+
$server = spawn(function() use ($s2c, $c2s) {
14+
$socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
15+
$s2c->send(stream_socket_get_name($socket, false));
16+
$client = stream_socket_accept($socket, 5);
17+
$s2c->send("accepted");
18+
$c2s->recv();
19+
fclose($client);
20+
$s2c->send("closed");
21+
fclose($socket);
22+
});
23+
24+
$client = spawn(function() use ($s2c, $c2s) {
25+
$address = $s2c->recv();
26+
$sock = stream_socket_client("tcp://$address", $errno, $errstr, 5);
27+
$s2c->recv();
28+
29+
fwrite($sock, "payload");
30+
echo "feof after send: " . (feof($sock) ? "true (BUG!)" : "false") . "\n";
31+
32+
$c2s->send("close now");
33+
$s2c->recv();
34+
35+
\Async\delay(1);
36+
37+
echo "feof after remote close: " . (feof($sock) ? "true" : "false (BUG!)") . "\n";
38+
39+
fclose($sock);
40+
});
41+
42+
await_all([$server, $client]);
43+
44+
?>
45+
--EXPECT--
46+
feof after send: false
47+
feof after remote close: true
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
feof() returns false when unread data is pending
3+
--FILE--
4+
<?php
5+
6+
use Async\Channel;
7+
use function Async\spawn;
8+
use function Async\await_all;
9+
10+
$s2c = new Channel(1);
11+
$c2s = new Channel(1);
12+
13+
$server = spawn(function() use ($s2c, $c2s) {
14+
$socket = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
15+
$s2c->send(stream_socket_get_name($socket, false));
16+
$client = stream_socket_accept($socket, 5);
17+
fwrite($client, "data in buffer");
18+
$s2c->send("sent");
19+
$c2s->recv();
20+
fclose($client);
21+
fclose($socket);
22+
});
23+
24+
$client = spawn(function() use ($s2c, $c2s) {
25+
$address = $s2c->recv();
26+
$sock = stream_socket_client("tcp://$address", $errno, $errstr, 5);
27+
$s2c->recv();
28+
29+
\Async\delay(1);
30+
31+
echo "feof with pending data: " . (feof($sock) ? "true (BUG!)" : "false") . "\n";
32+
33+
$data = fread($sock, 1024);
34+
echo "read: $data\n";
35+
echo "feof after reading all: " . (feof($sock) ? "true (BUG!)" : "false") . "\n";
36+
37+
$c2s->send("done");
38+
fclose($sock);
39+
});
40+
41+
await_all([$server, $client]);
42+
43+
?>
44+
--EXPECT--
45+
feof with pending data: false
46+
read: data in buffer
47+
feof after reading all: false

0 commit comments

Comments
 (0)