stream_copy_to_stream() with sockets insufficiently documented
Nobody has claimed this yet.
- Dominant language
- XML
- Stars
- 596
- Forks
- 890
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 55
Description
Description
The code is a little big, so let me explain first.
If the source socket was created as blocking, stream_copy_to_stream returns as soon as default_socket_timeout is reached, before the actual EOF happened.
That's probably the expected behaviour but we couldn't find this in the documentation, maybe that should be improved. I mean, the documentation doesn't mention that stream_copy_to_stream can return because of timeout which may cause a perception that it returns on one of 3 conditions:
- stream is over (EOF)
- max length is reached
- error
None of these conditions actually happens in our case
The behaviour changed recently if the source socket was created from stream_socket_accept on a non-blocking socket on Linux (it looks related to php/php-src#8472, php/php-src#9252).
The following code:
<?php
const SEND_BYTES = 10 * 1024;
if (pcntl_fork() == 0) {
doSender();
}
doServer();
pcntl_wait($status); // wait for the sender process
function doSender() {
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, '127.0.0.1', 9638);
$string = str_repeat("0", SEND_BYTES);
$written = 0;
for ($i = 0; $i < 2; ++ $i) {
$bytes = socket_write($socket, $string . "\n");
assert($bytes !== false, "server died");
$written += $bytes;
sleep((int)ini_get('default_socket_timeout') * 2);
}
printf("written: %d\n", $written);
exit;
}
function doServer() {
$server = new Server;
$server->doServer();
}
class Server
{
private $socket_server;
function doServer()
{
$this->socket_server = $this->startSocketServer();
while (true) {
//stream_socket_accept emits a warning if interrupted by a signal
if (@$client = stream_socket_accept($this->socket_server, 0)) {
$this->handleClient($client);
break;
}
usleep(10000); //0.01 sec
}
fclose($this->socket_server);
}
function startSocketServer()
{
$socket_server = stream_socket_server("tcp://0.0.0.0:9638", $errno, $errstr);
assert(!empty($socket_server), "Could not start socket server [$errno]: $errstr");
if (!stream_set_blocking($socket_server, false)) {
assert(false, "Could not set socket server to non-blocking mode");
}
return $socket_server;
}
protected function handleClient($client)
{
$handle = fopen("/dev/null", 'w');
assert(!empty($handle), "Could not open client output stream for write");
$bytes_copied = stream_copy_to_stream($client, $handle);
printf("read %d bytes\n", $bytes_copied);
fclose($handle);
fclose($client);
}
}
Resulted in this output:
$ php -ddefault_socket_timeout=2 socket-test.php
read 10241 bytes
written: 20482
But I expected this output instead (PHP 8.0.22 returned):
$ php -ddefault_socket_timeout=2 socket-test.php
written: 20482
read 20482 bytes
PHP Version
PHP 8.0.25
Operating System
CentOS Stream 8
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the PHP documentation for stream_copy_to_stream() and compare its stated return conditions with the socket example in the issue. Document that a socket read can stop when default_socket_timeout is reached, including how that affects the returned byte count; the existing example should make the behavior and completion criteria clear.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100