php / php/doc-en

stream_copy_to_stream() with sockets insufficiently documented

Open
#2,026 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug Category: Streams
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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.