ID:               39809
 User updated by:  e at osterman dot com
 Reported By:      e at osterman dot com
-Status:           Feedback
+Status:           Open
 Bug Type:         CGI related
 Operating System: FC6
 PHP Version:      5.2.0
 Assigned To:      dmitry
 New Comment:

Dimitry,

You're example shocked me. I tried something similar making a simple
stream_socket_server, but didn't make the client in PHP.

<?
   $server = stream_socket_server('tcp://127.0.0.1:1234');
    if (!$server) {
        die('Unable to create AF_INET socket');
    }
    sleep(10);
?>

# telnet localhost 1234
Trying 127.0.0.1...
telnet: Unable to connect to remote host: Connection refused

I had expected the same behavior in PHP's fsockopen, but indeed, as you
said fsocketopen returns a valid resource even though the connection has
not yet been accept()'d by the server. More over, feof returns false and
fwrite to the socket returns the correct number of bytes "written".
stream_get_metadata returns nothing interesting either. 

How is a PHP fsockopen/stream_socket_client client to know when a
connection truely has been accept()'d?

The reason why this is all so important to us, is that we have a
cluster of frontend servers that use a pool of FCGI backend servers. We
need the busier frontend servers to dynamically open up more persistent
connections (FCGI_KEEP_CONN) to FCGI servers (and release them as
demand subsides). When a particular FCGI server is at capacity
(PHP_FCGI_CHILDREN), we need the client to try (in a round robin
fashion) another FCGI server. The current problem, as you understand by
now, is that the client get's stuck when connecting to an FCGI server at
capacity and timeout. 

As you've now suggested, it's apparently b/c they getting stuck waiting
for the accept(), but never get it.

What recourse does the client have?


Regards,

Erik Osterman


Previous Comments:
------------------------------------------------------------------------

[2006-12-18 13:15:46] [EMAIL PROTECTED]

No you are :)
But you made a great job writting test script, and I think this
discussion may lead us to some good result.

The fact that connect() returns file descriptor doesn't mean that
accept() was really called. See the folllowing script. You can even
write to socket befor it is really accepted.

<?php
        $server = stream_socket_server('tcp://127.0.0.1:1234');
        if (!$server) {
                die('Unable to create AF_INET socket [server]');
        }


        $socket1 = fsockopen('127.0.0.1', 1234, $errno, $errstr, 5);
        if (!$socket1)
                die("Failed to connect to 127.0.0.1:1234\n");
        fwrite($socket1, "Hello\n");


        $socket2 = fsockopen('127.0.0.1', 1234, $errno, $errstr, 5);
        if (!$socket2)
                die("Failed to connect to 127.0.0.1:1234\n");
        fwrite($socket2, "World\n");

        $socket = stream_socket_accept($server);
        $data = fgets($socket, 1024);
        echo($data);
        fclose($socket);

        $socket = stream_socket_accept($server);
        $data = fgets($socket, 1024);
        echo($data);
        fclose($socket);

        fclose($socket2);
        fclose($socket1);
        fclose($server);
?>


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

[2006-12-15 21:35:04] e at osterman dot com

> PHP process DOESN'T accept() new connections 
> if it already has persistent connection opened. 
> Note that php/fastcgi is one-process-one-connection
> server that doesn't implement multiplexion

If this were actually the case, I'd be satisfied. That would mean the
FCGI clients would get "connection refused" when there are no more
sockets/children available. But what actually happens is that the
connection is established, meaning accept() does get called.

To test this, modify the example like this

// Open up the first connection
$socket1 = FCGI_Connect('localhost', 1234);
// Send a request with FCGI_KEEP_CONN
FCGI_Test($socket1);
// Open up the second connection (should be refused)
$socket2 = FCGI_Connect('localhost', 1234);

printf("socket1:%d socket2:%d\n", feof($socket1), feof($socket2));


Expected output:
socket1:0 socket2:1

Actual output:
socket1:0 socket2:0

In otherwords, both connections are established => accept() was
called.


Am I making sense?

Regards,

Erik Osterman

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

[2006-12-15 15:00:55] [EMAIL PROTECTED]

> it simply accepts/ignores them

PHP process DOESN'T accept() new connections if it already has
persistent connection opened. Note that php/fastcgi is
one-process-one-connection server that doesn't implement multiplexion
(like apache 1.3). 

PHP doesn't try to manage persistent connection itself, however FastCGI
module may do it (especially in multithreaded environment).

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

[2006-12-13 21:26:46] e at osterman dot com

I know if you open up 1 socket to one child, it works:

> If you only open up 1 socket, and run multiple requests > it works
fine.

That's not the bug. The bug is PHP doesn't handle persistent
connections (FCGI_KEEP_CONN), when the number of persistent connections
exceedes the number of php children. The fcgi spec states that if the
application doesn't have enough resoures to complete the request (e.g
database handles, or in the case of PHP enough children), that it
should return that it's overloaded. PHP does not do this; it simply
accepts/ignores them. What PHP does is rely on the connection queueing,
which doesn't solve the KEEP_CONN problem. Constantly opening up
connections is inefficient.



Regards,

Erik Osterman

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

[2006-12-13 14:44:51] [EMAIL PROTECTED]

In your example you use persistent FastCGI connections
(FCGI_KEEP_CONN). It means web server connects to PHP and sends SEVERAL
requests using the SAME socket then it can close connection. You can
correct your example in the following way to use persistent conection:

$socket1 = FCGI_Connect('localhost', 1234);
FCGI_Test($socket1);
FCGI_Response($socket1);
FCGI_Test($socket1);
FCGI_Response($socket1);

or you may not to use persistent connection and then you must close
connection 

$socket1 = FCGI_Connect('localhost', 1234);
FCGI_Test($socket1);
FCGI_Response($socket1);
fclose($socket1);
$socket2 = FCGI_Connect('localhost', 1234);
FCGI_Test($socket2);
FCGI_Response($socket2);
fclose($socket2);

In case of non-persistent connection usgage of shutdown() right after
sending request is much better then close() after reading response.


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

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/39809

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

Reply via email to