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:

> 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


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

[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.


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

[2006-12-13 06:55:06] e at osterman dot com

Reproduce Code:

<?
// test-fcgi.php - a sample FCGI client

define('FCGI_VERSION_1', 1);
define('FCGI_BEGIN_REQUEST', 1);
define('FCGI_ABORT_REQUEST', 2);
define('FCGI_END_REQUEST', 3);
define('FCGI_PARAMS', 4);
define('FCGI_STDIN', 5);
define('FCGI_STDOUT', 6);
define('FCGI_STDERR', 7);
define('FCGI_DATA', 8);
define('FCGI_GET_VALUES', 9);
define('FCGI_GET_VALUES_RESULT', 10);

define('FCGI_RESPONDER', 1);
define('FCGI_KEEP_CONN', 1);

function FCGI_Packet($type, $content)
{
  $len=strlen($content);
 
$packet=chr(FCGI_VERSION_1).chr($type).chr(0).chr(1).chr((int)($len/256)).chr($len%256).chr(0).chr(0).$content;
  return($packet);
}

function FCGI_NVPair($name, $value)
{
  $nlen = strlen($name);
  $vlen = strlen($value);

  if ($nlen < 128)
    $nvpair = chr($nlen);
  else
    $nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) .
chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);

  if ($vlen < 128)
    $nvpair .= chr($vlen);
  else
    $nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) .
chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
  return $nvpair . $name . $value;
}

function FCGI_Decode($data)
{
  if( strlen($data) < 8 )
    die("Packet too small " . strlen($data) . "\n");
  $length = (ord($data{4}) << 8)+ord($data{5});
  $packet = Array( 'version' => ord($data{0}),
                   'type'    => ord($data{1}),
                   'length'  => $length,
                   'content' => substr($data, 8, $length) );

  return $packet;

}

function FCGI_Connect($host, $port) {

  // Connect to FastCGI server
  $socket = fsockopen($host, $port, $errno, $errstr, 5);
  if( !$socket )
    die("Failed to connect to $host:$port\n");
  return $socket;
}
function FCGI_Test($socket)
{
  // Begin session
  $packet = '';
  $packet .= FCGI_Packet(FCGI_BEGIN_REQUEST,
chr(0).chr(FCGI_RESPONDER).chr(FCGI_KEEP_CONN).chr(0).chr(0).chr(0).chr(0).chr(0)
);

  // Build params

  $params = '';
  $params .= FCGI_NVPair('GATEWAY_INTERFACE', 'FastCGI/1.0');
  $params .= FCGI_NVPair('REQUEST_METHOD', 'GET');
  $params .= FCGI_NVPair('SCRIPT_FILENAME', '/tmp/test.php');

  $packet .= FCGI_Packet(FCGI_PARAMS, $params);
  $packet .= FCGI_Packet(FCGI_PARAMS, null);
  $packet .= FCGI_Packet(FCGI_STDIN, null);

  fwrite($socket, $packet);
}

function FCGI_Response($socket)
{
  // Read answers from fastcgi server
  while(true)
  {
    if(feof($socket))
      die("Socket closed\n");
    $packet = fread($socket, 8);
    if( $packet === false )
      die("Read failed\n");
    $header = FCGI_Decode($packet);
    //print_r($header);
    $len=$header['length']%8;
    $padlen=($len?(8-$len):0);
    $packet .= fread($socket, $header['length']+$padlen);
    $response = FCGI_Decode($packet);
    if( $response['type'] == FCGI_END_REQUEST )
      break;
    else
      print "[{$response['type']}] [{$response['content']}]\n";
  }
}

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


<?
// /tmp/test.php - a sample cgi
echo "Hello World\n";
?>

Then start php-cgi in single process mode. 

php-cgi -b 1234

Run test-fcgi.php. The second request will never return. If you only
open up 1 socket, and run multiple requests it works fine.

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

[2006-12-13 03:27:22] e at osterman dot com

Description:
------------
Dimitry states in bug #37422:
> The "indle timeout" error is not a bug. It may occur
> on high load, then requests concurrency is larger
> then number of running PHP processes.

This is the incorrect behavior and makes it impossible to detect when
php-cgi is taking forever or simply ignoring you. 

According to the FastCGI specification, when the server is overloaded
(e.g. connections exceed PHP_FCGI_CHILDREN), it should respond with
FCGI_END_REQUEST packet with the protocolStatus flag of
FCGI_OVERLOADED. The current behavior is php-cgi accepts the request,
but never responds -- simply discarding it. It would be much better if
PHP responded with FCGI_OVERLOADED, or simply rejected connections when
it is too busy.

http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S5.5


Reproduce code:
---------------
To reproduce, open up more than PHP_FCGI_CHILDREN connections to the
php-cgi server. All connections will be accepted and all will accept
packets, but only PHP_FCGI_CHILDREN of the connections will ever return
a response. The rest of the connections will simply accept the request
and do nothing.

Expected result:
----------------
Server should respond with FCGI_END_REQUEST packet with the
protocolStatus flag of FCGI_OVERLOADED or simply reject connections
when connections exceed PHP_FCGI_CHILDREN.

Actual result:
--------------
php-cgi never responds; request is lost.


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


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

Reply via email to