[php-src] Issue #13887: stream_select fails on blocking sockets except for incoming connections
| From: | R3kyr | Date: | Fri, 05 Apr 2024 07:39:17 +0000 |
| Subject: | [php-src] Issue #13887: stream_select fails on blocking sockets except for incoming connections | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-246705@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/13887
Author: R3kyr
### Description
Passing blocking sockets created with
stream_socket_server() and
stream_socket_client() to stream_select() throws a ValueError except for
blocking sockets created with stream_socket_accept().
Tested with PHP 8.3.0 and 8.3.4, both ZTS on Win 10 x64 latest build and Ubuntu 22.04 LTS x64.
Example code that demonstrates the issue:
server.php
```php
<?php
$server = stream_socket_server("127.0.0.1:0", $c, $e);
[, $port] = explode(":", stream_socket_get_name($server, false));
$process = proc_open(
"\"" . PHP_BINARY . "\" " . __DIR__ . DIRECTORY_SEPARATOR .
"client.php --port=$port",
[["pipe", "r"], ["pipe", "w"], ["pipe",
"w"]],
$Pipes
);
$client = stream_socket_accept($server);
var_dump(stream_get_meta_data($client)["blocked"]); //true
$_ = null;
$read = [$client];
//Select on inbound blocking client socket works.
while(!stream_select($read, $_, $_, 0)) {
usleep(1000);
}
$line = fgets($client);
echo $line;
fwrite($client, "Hello from server!" . PHP_EOL);
//Select on blocking server socket fails.
$read = [$server];
while(!stream_select($read, $read, $read, 0)) {
usleep(1000);
}
```
client.php
```php
<?php
$port = getopt("", ["port::"])["port"];
$client = stream_socket_client("127.0.0.1:$port", $c, $e);
$written = fwrite($client, "Hello from client!" . PHP_EOL);
try {
$_ = null;
$read = [$client];
//Select on outbound blocking client socket fails.
while(!stream_select($read, $_, $_, 0)) {
usleep(1000);
}
$line = fgets($client);
file_put_contents("child.txt", $line);
} catch(ValueError $e) {
file_put_contents("child.txt", (string)$e);
}
```
This is kinda inconvenient if using a wrapper-class that provides async blocking read/write
operations
as it requires temporarily switching the socket to non-blocking mode.
```php
class Socket {
public function __set(...) {
stream_set_blocking($this->handle, $this->blocking = $value);
}
public function read(int $amount) {
return fread($this->handle, $amount);
}
public function readAsync(int $amount) {
Fiber::suspend();
if(!$this->blocking) {
return fread($this->handle, $amount);
}
stream_set_blocking($this->handle, false);
$_ = null;
$read = [$this->handle];
while(!stream_select($read, $_, $_, 0)) {
Fiber::suspend();
}
stream_set_blocking($this->handle, true);
return fread($this->handle, $amount);
}
}
```
### PHP Version
PHP 8.3.4
### Operating System
_No response_