1

Note: this is the same as this SO post, but is possibly more appropriate here as I suspect the problem is server config related rather than code related.

I'm using a LAMP setup with PHP running through mod_fcgid. For most requests this works well, but I've noticed that when I download a file but interrupt the download before it's complete, the php-cgi process that was serving the file blocks attempting to write more data until it the IPCCommTimeout is reached. Once the timeout is reached, the process gets interrupted and the process starts servicing other requests again.

This problem does not occur if I use mod_php instead of mod_fcgid.

Is there some available setting for fcgid that I can set to have it abort if nothing is capturing the output? Is there something I can do in PHP to handle it?

The problem doesn't occur if the download isn't interrupted; in fact, I only noticed it because I was trying to stream a FLV file using gddflvplayer, which seems to send a brief request to get the first few frames (which it shows as a preview), then another to play it, and this causes the same problem.

FYI, this is the strace of the hanging cgi process; it sits like this until it's eventually interrupted, presumably by the process manager when the IPCCommTimeout is reached. My guess is that it's hanging trying to output the results of the readfile() call, but Apache is no longer listening (as the request has been cancelled by the user).

root@some-machine:~# strace -p 24837
Process 24837 attached - interrupt to quit
write(3, "\5|A\313%\35\337\376\275\237\230\266\242\371\37YjzD<\322\215\357\336:M\362P\335\242\214\341"..., 17432

The logs indicate that the request is eventually reaped due to the timeout

mod_fcgid: read data timeout in 240 seconds

The download code more or less just uses readfile to serve up the file, with a few headers involved as well (note: in this code, Header is more or less just a wrapper around header() to avoid problems in tests).

$filepath    = '/some/path/foo.flv';
$filename    = 'foo.flv';
$disposition = 'inline';

$h = Header::get();
$h->send('Pragma: public');
$h->send('Content-Transfer-Encoding: binary');
$h->send('Content-type: ' . FileSystem::get()->getMimeType($filepath));
$h->send('Content-Length: ' . FileSystem::get()->getFileSize($filepath));
$h->send('Content-Disposition: ' . $disposition . '; filename="' . $filename . '"');
$h->send('Content-transfer-encoding: 8bit');
$h->send('Expires: 0');
$h->send('Pragma: cache');
$h->send('Cache-Control: private');

flush();
readfile($filepath);

The server itself is running Debian Lenny with standard packages for php5-cgi, apache2, libapache2-mod-fcgid, but I also get the same results on a development box with Ubuntu 10.10.

Package information follows -

[foo@bar ~]$  dpkg -l | egrep '(apache2|php5)'
ii  apache2-mpm-worker                  2.2.9-10+lenny9            Apache HTTP Server - high speed threaded model
ii  apache2-utils                       2.2.9-10+lenny9            utility programs for webservers
ii  apache2.2-common                    2.2.9-10+lenny9            Apache HTTP Server common files
ii  libapache2-mod-fastcgi              2.4.6-1                    Apache 2 FastCGI module for long-running CGI scripts
ii  libapache2-mod-fcgid                1:2.2-1+lenny1             an alternative module compat with mod_fastcgi
ii  php5                                5.2.6.dfsg.1-1+lenny9      server-side, HTML-embedded scripting language (metapack
ii  php5-cgi                            5.2.6.dfsg.1-1+lenny9      server-side, HTML-embedded scripting language (CGI bina
ii  php5-cli                            5.2.6.dfsg.1-1+lenny9      command-line interpreter for the php5 scripting languag
ii  php5-common                         5.2.6.dfsg.1-1+lenny9      Common files for packages built from the php5 source
ii  php5-curl                           5.2.6.dfsg.1-1+lenny9      CURL module for php5
ii  php5-ffmpeg                         0.5.3.1-3                  ffmpeg support for php5
ii  php5-fileinfo                       1.0.4-1                    Fileinfo module for PHP 5
ii  php5-gd                             5.2.6.dfsg.1-1+lenny9      GD module for php5
ii  php5-imagick                        2.1.1RC1-1                 ImageMagick module for php5
ii  php5-mysql                          5.2.6.dfsg.1-1+lenny9      MySQL module for php5
ii  php5-suhosin                        0.9.27-1                   advanced protection module for php5
El Yobo
  • 926
  • 1
  • 8
  • 11

3 Answers3

2

We could see your problem as 'not really a problem', as when timeout occurs the php script ends. If it was not ending after the timeout you would get in bigger problems :-). Then to reduce the hangs time you would at least be able to play with the FcgidBusyTimeout & FcgidBusyScanInterval parameters, http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidbusytimeout

Now, effectively apache does not send any information on client tcp/ip closure to the fcgid backend. A search on comet things on Stack overflow gives this excellent response: https://stackoverflow.com/questions/1354690/http-proxy-fastcgi-scgi-not-closing-connection-when-client-disconnected-bug-or/1384620#1384620 , where bbum gives a link to a mod-fastcgi patch, if you really want to handle the premature ending things.

regilero
  • 1,470
  • 1
  • 9
  • 14
  • The problem does resolve itself after the timeout, true, but it seems that fcgid doesn't realise that the cgi process is not responding and continues to send requests to it (at least, for the original browser that interrupted the download - not sure about others), which hang pending the timeout. So if I interrupt a download, then try to refresh the page, it will hang until the timeout occurs. If I open another browser and request a page, it works (and spans additional CGI processes as necessary). – El Yobo Jan 04 '11 at 09:42
  • you can as well check for apache way of detecting theses client breaks with netstat -nalp |grep ':80 '|grep "WAIT" . check if you have some FIN_WAIT_2 sockets for example. And check that you have a recent apache, to get all bugfixes. check "lingering close" here: http://httpd.apache.org/docs/2.2/misc/perf-tuning.html or that http://httpd.apache.org/docs/1.3/misc/fin_wait_2.html (older). if your problem comes from bad client close you can as well try to force your file upload in non-keepalive requests by setting the nokeepalive apache env variable for theses specific url. – regilero Jan 04 '11 at 11:40
1

The problem comes down to PHP session locking; for some reason mod_php manages to unlock the session when the request is cancelled, but mod_fcgid doesn't in this case. Calling session_write_close() before readfile() (100% safe, as I'm not going to be doing anything after outputting the file anyway, as it would corrupt it) ensures the session lock is released and prevents the system hanging for that user.

El Yobo
  • 926
  • 1
  • 8
  • 11
0

You may want to check the ignore_user_abort setting and the max_execution_time setting.

StasM
  • 153
  • 1
  • 1
  • 7
  • Good suggestion, but it doesn't seem to be the problem; the execution time is already being obeyed, and the ignore_user_abort setting is disabled. – El Yobo Jan 04 '11 at 09:43