php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #52602 fread is blocking even with the use of stream_select
Submitted: 2010-08-14 03:07 UTC Modified: 2017-10-24 05:45 UTC
Votes:9
Avg. Score:4.8 ± 0.6
Reproduced:6 of 7 (85.7%)
Same Version:0 (0.0%)
Same OS:0 (0.0%)
From: g at gsxm dot net Assigned: cataphract (profile)
Status: Assigned Package: Sockets related
PHP Version: 5.2.14 OS: Gentoo x86_64
Private report: No CVE-ID: None
Password:
Status:
Package:
Bug Type:
Summary:
From: g at gsxm dot net
New email:
PHP Version: OS:

 

 [2010-08-14 03:07 UTC] g at gsxm dot net
Description:
------------
Using PHP CLI
Gentoo 64 bit box
PHP version 5.2.14
clean compile
Dual AMD Opteron 2212 CPU

Something is blocking in the code, it seems to be fread. I am sure this is not the expected result.

I greatly simplified the sample I included.

I can just put the socket/stream in non-blocking mode, but I would much prefer to use the code as I designed it. 

I have considered just using the socket functions in php or just writing the code in C.

Once in a great while it will resume after about thirty to sixty seconds. In non-blocking mode I have no issues. This is a php cli script that connects to another daemon running on the same machine, I am using this php script to parse data to place in a sql database.

Test script:
---------------
$sock=@fsockopen("localhost","7777",$errno,$errstr,3);
if (!$sock)
{ 
    echo "sock error".$crlf;
}

while(true)
{
    $read=array($sock);
    $write=NULL;
    $except=NULL;
    $status=@stream_select($read,$write,$except,1);
    if ($status===false)
    {
        echo "select error".$crlf;
        exit();
    }
    if ($status>0)
    {
        $data =  @fread($sock,128);
        echo $data;
        $out .= $data;
    }
    
    usleep(10000);
}

Expected result:
----------------
The loop should run for eternity as expected.

Actual result:
--------------
The loop is blocked, I am presuming by fread due to a failure of the expected behavior of stream_select.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-08-14 05:35 UTC] g at gsxm dot net
Not sure why I did not think of this before, but perhaps only the stream-* functions works with stream_select
 [2010-09-27 01:30 UTC] cataphract@php.net
-Status: Open +Status: Feedback -Assigned To: +Assigned To: cataphract
 [2010-09-27 01:30 UTC] cataphract@php.net
-Status: Feedback +Status: Verified
 [2010-09-27 04:09 UTC] cataphract@php.net
fread is not really adequate for this because of its buffering (see stream_socket_recvfrom()).

Since you are specifying a length of 128 to fread, if you receive say, a 138 bytes long payload, the first call to fread will to read into the stream buffer the whole data, but will only return 128 bytes. The stream buffer remains with 10 bytes, but the socket buffer will be empty. On the second call to fread, it will try to read into the stream buffer again, but this time the socket buffer is empty, so it will block until it can read another packet or until the timeout.

This appears, however, to be the desired behavior (perhaps fread could just return what it has in the buffer if it cannot read anything immediately, i.e., if it would otherwise block). So stream_select implements an emulation behavior that says the stream is readable without calling select if the there's data in the stream buffer. This is indeed odd, because the usual semantics of select are that it a read on a socket returned in the readfs set will not block.
 [2010-09-27 04:16 UTC] cataphract@php.net
To sum up, the emulation behavior of stream_select is worse than useless:

* If using stream_socket_recvfrom(), the buffer will be bypassed anyway, so the emulation behavior won't kick in.
* If using fread() and there's something in the buffer, it will return the stream in the readfs set, which is documented "to see if characters become available for reading (more precisely, to see if a read will not block [...])", but a subsequent fread call will nevertheless eventually block when trying to fill the buffer.
 [2013-01-06 09:01 UTC] gauthierm@php.net
This problem affected me while writing a WebSocket server in PHP. 
stream_select() returns streams ready for reading and fread() blocks when I try 
to read them. What is the point of stream_select() if it behaves like this? How 
are you supposed to write a blocking I/O loop?

My solution was to use stream_socket_recvfrom() and give up on implementing 
SSL/TLS for my server (making me a bit sad inside).

Since stream_select() returns the stream ready for reading, I would expect 
fread() to return the available bytes from the stream buffer in this case rather 
than blocking. See Bug 51056 for a long discussion on fread() behaviour with no 
conclusion.

Can some conclusion be reached for PHP? Is there any actual evidence of people 
writing their servers in a way where fixing fread() will break?
 [2013-01-06 09:23 UTC] gauthierm@php.net
As another workaround, you can use the same chunk size in your read calls as the 
PHP stream chunk size. Then there is never buffered data that has not been 
consumed. In most cases the chunk size is 8192. In PHP 5.4 I think you can set the 
chunk size using stream_set_chunk_size().

This workaround allows you to continue using fread().
 [2014-07-18 12:18 UTC] rob at associatedtechs dot com
I just got bit by this exact bug after spending a while trying to figure out what the heck was going on. PHP version 5.5.10-1, Debian x86_64.

gauthierm's solution prevents fread() from blocking, but I can only get the first 8k chunk. stream_select() subsequently returns 0 even though there's data left in the stream. I understand why it does that, but this makes fread() and stream_select() impossible to reliably use together, which means non-blocking functions have to be used for TLS connections.
 [2017-10-24 05:45 UTC] kalle@php.net
-Status: Verified +Status: Assigned
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Nov 19 01:31:42 2017 UTC