php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #54062 PHP does not notice user abort
Submitted: 2011-02-21 15:44 UTC Modified: 2011-02-21 22:53 UTC
From: james dot mk dot green at gmail dot com Assigned:
Status: Not a bug Package: Network related
PHP Version: Irrelevant OS: Linux/Windows
Private report: No CVE-ID: None
 [2011-02-21 15:44 UTC] james dot mk dot green at gmail dot com
Description:
------------
Referencing http://php.net/manual/en/features.connection-handling.php

I understand that providing PHP is writing to or reading from the web server connection, a registered shutdown function should be called should the client abort.

I have never known this to be the case.

I attach a simple script. It attempts to sleep then write back to the client. It has a registered shutdown function that checks connection_status() and connection_aborted(). According to my syslog however, the full script executes followed by the shutdown function which says the user is still connected, despite my pressing the browser stop button immediately after placing the request.

I have tested this on Windows using Lighttpd with PHP 5.2, and on Ubuntu with Apache preforking PHP 5.3. Both exhibit the same behaviour.

These tests were conducted with ignore_user_abort(true) and (false) - no difference observed so I removed it.


Test script:
---------------
<?php
syslog(LOG_DEBUG, 'Connection opened');
register_shutdown_function('shutdown');
$usleep = 2000000;
for ($i=0; $i < 100; $i++) {
    $str[$i] = '';
    for ($x=0; $x < 1000; $x++) {
        $str[$i] .= 'flubber';
    }
}
if (isset($_REQUEST['usleep'])) {
    $usleep = (int) $_REQUEST['usleep'];
}
usleep($usleep);
syslog(LOG_DEBUG, 'Completed usleep()');
echo "Thanks for waiting\n";
print_r($str);
ob_end_flush();
syslog(LOG_DEBUG, 'ob_end_flush() called');
flush();
syslog(LOG_DEBUG, 'Have flushed()');
// We get to this part regardless of having already pressed STOP
function shutdown()
{
    echo "Thanks for waiting\n";
    flush();
    syslog(LOG_DEBUG, 'Echo completed');
//    sleep(1);
    syslog(LOG_DEBUG, 'Have slept');
    syslog(LOG_DEBUG, 'Shutdown detected.');
    syslog(LOG_DEBUG, "The result of connection_status() is: " . connection_status());
    // The above is almost always 0
    if (connection_aborted()) {
        // This almost never happens!
        syslog(LOG_DEBUG, 'The connection has aborted');
    } else {
        // This almost always happens!
        syslog(LOG_DEBUG, 'The connection remains');
    }
}


Expected result:
----------------
syslog entries should stop when PHP writes to the client socket and notes that the client has sent an abort signal. Then, the shutdown function should show that the connection_status() is no longer 0, and that the connection_aborted() method returns true.

Actual result:
--------------
syslog entries continue to Have flushed(), then show that "The result of connection_status() is: 0." and "The connection remains."

This should not be the case. Perhaps Apache is not sending the appropriate signal, yet neither is Lighttpd..?

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2011-02-21 16:19 UTC] ceo at l-i-e dot com
"I have never known this to be the case."
...
$usleep = 2000000;
...
usleep($usleep);
...
    if (connection_aborted()) {
        // This almost never happens!
        syslog(LOG_DEBUG, 'The connection has aborted');
    } else {

"almost never" or "never"?

Not really the same...
Not sure PHP even processes and abort in the middle of usleep.
2 seconds is a long time in a normal request.
Try shorter usleep.
And when you *do* get an abort, call exit to really really really END the script, bypassing any other shutdown functions.
 [2011-02-21 16:50 UTC] james dot mk dot green at gmail dot com
The script attached is a test case. The "real script" that triggered this report does not use sleep or similar. And yes, on two-three attempts out of several dozen I have seen a client has aborted message. However, statistically this is beyond unreliable.

Further to the original report I have spoken in ##php on Freenode and tried their suggestions without any luck.

The "real script" does quite a lot of backend processing while a software client waits. If the client then times out waiting, the server script continues to send back the data processed and says that the client remains connected at the very end (within the shutdown callback). This led me to my testing of connection handling and this test case.
 [2011-02-21 17:11 UTC] cataphract@php.net
Works fine here:

Normal circumstances:
Feb 21 16:06:33 damnation apache2: Connection opened
Feb 21 16:06:35 damnation apache2: Completed usleep()
Feb 21 16:06:35 damnation apache2: ob_end_flush() called
Feb 21 16:06:35 damnation apache2: Have flushed()
Feb 21 16:06:35 damnation apache2: Echo completed
Feb 21 16:06:35 damnation apache2: Have slept
Feb 21 16:06:35 damnation apache2: Shutdown detected.
Feb 21 16:06:35 damnation apache2: The result of connection_status() is: 0
Feb 21 16:06:35 damnation apache2: The connection remains

Stop button:
Feb 21 16:06:44 damnation apache2: Connection opened
Feb 21 16:06:46 damnation apache2: Completed usleep()
Feb 21 16:06:46 damnation apache2: ob_end_flush() called
Feb 21 16:06:46 damnation apache2: Have flushed()

ignore_user_abort(true) + Stop button:
Feb 21 16:07:59 damnation apache2: Connection opened
Feb 21 16:08:01 damnation apache2: Completed usleep()
Feb 21 16:08:01 damnation apache2: ob_end_flush() called
Feb 21 16:08:01 damnation apache2: Have flushed()
Feb 21 16:08:01 damnation apache2: Echo completed
Feb 21 16:08:01 damnation apache2: Have slept
Feb 21 16:08:01 damnation apache2: Shutdown detected.
Feb 21 16:08:01 damnation apache2: The result of connection_status() is: 1
Feb 21 16:08:01 damnation apache2: The connection has aborted

(Ubuntu 10.10, PHP 5.3.3-1ubuntu9.3, Apache/2.2.16)
 [2011-02-21 17:31 UTC] james dot mk dot green at gmail dot com
Feb 21 16:28:57 blofeld apache2: The connection remains
Feb 21 16:29:41 blofeld apache2: Connection opened
Feb 21 16:29:43 blofeld apache2: Completed usleep()
Feb 21 16:29:43 blofeld apache2: ob_end_flush() called
Feb 21 16:29:43 blofeld apache2: Have flushed()
Feb 21 16:29:43 blofeld apache2: Echo completed
Feb 21 16:29:43 blofeld apache2: Have slept
Feb 21 16:29:43 blofeld apache2: Shutdown detected.
Feb 21 16:29:43 blofeld apache2: The result of connection_status() is: 0
Feb 21 16:29:43 blofeld apache2: The connection remains

That's what I get using Firefox with PHP 5.3.3-1ubuntu9.3 on apache 2.2.16-1ubuntu3.1 using ubuntu Ubuntu 10.10.

All I do is type in the url of my disconnect.php, hit enter then immediately hit the stop button.
 [2011-02-21 17:39 UTC] johannes@php.net
-Status: Open +Status: Feedback
 [2011-02-21 17:39 UTC] johannes@php.net
Please try using this snapshot:

  http://snaps.php.net/php5.3-latest.tar.gz
 
For Windows:

  http://windows.php.net/snapshots/

Please use vanilla PHP from php.net. We have no idea what kind of patches Ubuntu applies and what they might break.
 [2011-02-21 17:42 UTC] james dot mk dot green at gmail dot com
johannes, it's been tested on a Windows server too (same behaviour). Besides, cataphract seems to be using Ubuntu's PHP package too.

Perhaps it's something to do with web server configuration? I'm using CGI on Windows, and mod_php on Ubuntu (Apache Prefork MPM). What might cause this behaviour?
 [2011-02-21 19:21 UTC] rasmus@php.net
Works fine for me on a Debian box running mod_php with Apache prefork, and a 
Centos box running nginx and php-fpm.
 [2011-02-21 19:40 UTC] rasmus@php.net
By the way, why so complicated a test?

Try the simple case:

<?php
ignore_user_abort(false);
for($i=0;$i<1000;$i++) {
	sleep(1);
	$str = "Line $i\n";
	echo $str;
	flush();
	file_put_contents("/tmp/heartbeat.log", $str, FILE_APPEND);
}


Things that might get in the way of this working for you would be if you have 
some sort of reverse proxy cache in front of your web server. Like a Cloudflare, 
for example. Or your own setup. You could also have multiple levels of output 
buffering going on. You could check that case with an ob_get_level() call.
 [2011-02-21 22:46 UTC] james dot mk dot green at gmail dot com
-Status: Feedback +Status: Open
 [2011-02-21 22:46 UTC] james dot mk dot green at gmail dot com
As unlikely as it sounds this may be an issue within Firefox.

I pasted the code from Rasmus onto the web server next door to mine. Fired it up 
in Firefox and PHP detected the user abort. Repeated my script but again it did 
not work. Slowly I worked the code from Rasmus directly in to my script but 
still no joy. At this point all that was different was the name of the shutdown 
function. Then tried using wget as the client and both scripts work. Repeated 
Rasmus' script from Firefox and it no longer detected user abort.

So we have a bug within the client - as far as I can tell. I am least happy I 
can work with this, thanks for all the tests and sorry for wasting people's 
time!
 [2011-02-21 22:53 UTC] johannes@php.net
-Status: Open +Status: Bogus
 [2011-02-21 22:53 UTC] johannes@php.net
Marking bug as user error.
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Wed Aug 21 13:01:35 2019 UTC