Bug #51056 [Fbk]: fread() on blocking stream will block even if data is available

From: Date: Thu, 11 Mar 2010 19:27:00 +0000
Subject: Bug #51056 [Fbk]: fread() on blocking stream will block even if data is available
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-148530@lists.php.net to get a copy of this message
Edit report at http://bugs.php.net/bug.php?id=51056&edit=1

 ID:               51056
 Updated by:       magicaltux@php.net
 Reported by:      magicaltux@php.net
 Summary:          fread() on blocking stream will block even if data is
                   available
 Status:           Feedback
 Type:             Bug
 Package:          Streams related
 Operating System: Linux Gentoo 2.6.32
 PHP Version:      5.3.1

 New Comment:

> This will block anyway when the buffer is empty and you won't be able
to known 
when it is empty, so you can't rely on this (sometimes it will block,
sometimes 
not).

PHP always calls poll() before read, so it knows if there is nothing to
read. 
stream_select() will return the socket as "ready" if there is data
pending in 
php buffer (even if there's no data on the socket), just so we can read
it.

> Also, some applications may rely on the blocking and will break if it
is 
changed. This behavior exists since at least PHP 5.1.

fread() manual explicitly warns about this:

When reading from anything that is not a regular local file, such as
streams 
returned when reading remote files or from popen() and fsockopen(),
reading will 
stop after a packet is available. This means that you should collect the
data 
together in chunks as shown in the examples below.

On the contrary, using blocking streams together with stream_select()
may lead 
to async program blocking because stream_select() saw there was pending
data, 
but a new packet will not arrive anytime soon.

> As this is not the normal case I would suggest to introduce some
timeout 
handling (this is what applications like e.g. Apache does, I guess), or
fixing 
what prevents you from using non blocking i/o with SSL streams instead.

It is the normal case to receive less than expected data as documented
on the 
php manual.
Apache (or any correctly coded networking app) does not uses timeouts
(except to 
detect dead clients), instead it uses read() which is reliable (ie. not
hang 
when there is data that can be returned).

By the way I have looked at what causes the problem I have with SSL
streams, and 
it could be worked around by switching the streamd between blocking mode
and 
non-blocking mode depending on the situation, however I would prefer to
avoid 
that (and it doesn't change the fact that fread() does not comply with
what is 
expected from it, both from read() syscall behaviour and php's manual)


Previous Comments:
------------------------------------------------------------------------
[2010-03-11 16:51:20] lbarnaud@php.net

> When data is already available in buffer, an information should be
passed to the lower-level read() to let it know it should not block.

This will block anyway when the buffer is empty and you won't be able to
known when it is empty, so you can't rely on this (sometimes it will
block, sometimes not).

Also, some applications may rely on the blocking and will break if it is
changed. This behavior exists since at least PHP 5.1.

> Considering any application handling data from network should handle
cases when received data is not complete

As this is not the normal case I would suggest to introduce some timeout
handling (this is what applications like e.g. Apache does, I guess), or
fixing what prevents you from using non blocking i/o with SSL streams
instead.

------------------------------------------------------------------------
[2010-03-11 02:21:59] magicaltux@php.net

Hi,

I know about fread() returning less data than asked for, however I could
not 
modify this behaviour without passing some kind of value to lower-level
read 
operation, which will call poll() if socket is blocking.
When data is already available in buffer, an information should be
passed to the 
lower-level read() to let it know it should not block.

The only non-intrusive solution to fix this would be to temporarly pass
socket 
in non-blocking mode if data was found in PHP buffer.

Considering any application handling data from network should handle
cases when 
received data is not complete, I believe it was best to return
immediatly if 
data is found and let the application call fread() again rather than
trying to 
workaround this problem with a dirty solution like passing temporarly in
non-
blocking mode.
Another solution would be to add an argument to the internal read call
("do not 
block") however it would change the API for the internal stream api, and
would 
require the argument to be handled into each stream wrapper.

------------------------------------------------------------------------
[2010-03-11 02:05:59] lbarnaud@php.net

Hi,

I made a test case for this ( 51056.phpt.txt )

fread() in C has exactly the same behavior, it will block if you try to
read more bytes than available.

Your patch correctly avoids this, however it introduces an other issue:
fread() will return less data than asked for, even if enough data is
available ( 51056-2.phpt.txt ).

------------------------------------------------------------------------
[2010-02-17 16:00:07] jani@php.net

btw. If you really want someone to do something about this, post the
patch to internals@lists.php.net as well. :)

------------------------------------------------------------------------
[2010-02-17 05:39:56] magicaltux@php.net

While for file sockets it makes sense when reading data to make sure a 
buffer gets completly filled until EOF is reached, this is not true 
for sockets.

There is already a condition for "greedy read", but it does not apply 
when we first restore data from the read buffer.

Here is a diff (PHP_5_3) that solves the issue:

Index: main/streams/streams.c
===================================================================
--- main/streams/streams.c	(révision 295152)
+++ main/streams/streams.c	(copie de travail)
@@ -592,6 +592,10 @@
 			size -= toread;
 			buf += toread;
 			didread += toread;
+
+			/* avoid trying to read if we already have 
data to pass */
+			if (stream->wrapper != 
&php_plain_files_wrapper)
+				break;
 		}
 
 		/* ignore eof here; the underlying state might have 
changed */


Test script output:

Testing PHP version: 5.3.3-dev
fread took 0.07ms to read 8 bytes
fread took 5.07ms to read 256 bytes
fread took 0.00ms to read 45 bytes
fread took 1000.11ms to read 8 bytes
fread took 5.02ms to read 256 bytes
fread took 0.00ms to read 45 bytes
fread took 1000.13ms to read 8 bytes
fread took 5.04ms to read 256 bytes


------------------------------------------------------------------------


The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at

    http://bugs.php.net/bug.php?id=51056


-- 
Edit this bug report at http://bugs.php.net/bug.php?id=51056&edit=1


Thread (21 messages)

« previous php.bugs (#148530) next »