ID: 39809 Updated by: [EMAIL PROTECTED] Reported By: e at osterman dot com -Status: Open +Status: Assigned Bug Type: CGI related Operating System: FC6 PHP Version: 5.2.0 Assigned To: dmitry
Previous Comments: ------------------------------------------------------------------------ [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. ------------------------------------------------------------------------ [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. ------------------------------------------------------------------------ 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