PHP script doesn't exit on browser exit - php

Why this dummy script keeps running event if the client closes the browser (so the connection to the server)?
while ( true )
{
sleep( 1 );
file_put_contents( '/tmp/foo' , "I'm alive ".getmypid()."\n" , FILE_APPEND );
}
It's unexpected to me according to this. Also this example doesn't seem to work.
And set_time_limit with a non-zero parameter simply does nothing.
I'd like some clarifications.

If you try to write some output to the browser in that loop, you should find the script aborts if the connection has been terminated. This behaviour is hinted at in the documentation for ignore_user_abort
When running PHP as a command line script, and the script's tty goes
away without the script being terminated then the script will die the
next time it tries to write anything, unless value is set to TRUE
I tried a few experiments myself, and found that even if you do attempt some browser output, the script will keep running if the output buffer isn't full yet. If you turn output buffering off, the script will abort when output is attempted. This makes sense - the SAPI layer should notice the request has been terminated when it tries to transmit the output.
Here's an example...
//ensure we're not ignoring aborts..
ignore_user_abort(false);
//find out how big the output buffer is
$buffersize=max(1, ini_get('output_buffering'));
while (true)
{
sleep( 1 );
//ensure we fill the output buffer - if the user has aborted, then the script
//will get aborted here
echo str_repeat('*', $buffersize)."\n";
file_put_contents( '/tmp/foo' , "I'm alive ".getmypid()."\n" , FILE_APPEND );
}
That demonstrates what triggers the abort. If you had a script which was prone to enter an endless loop with no output, you could use connection_aborted() to test whether the connection is still open.

set_time_limit only restricts the time spent in php code. Most of the time(I'd guess >99.9%) in your program is spent in system calls, writing data to a file and sleeping.
ignore_user_abort only aborts when you're writing something to the client (not in a local file) - there's simply no way you can distinguish an unused and a terminated connection otherwise in TCP, unless the client excplicitely terminates the connection with an RST packet.

Related

PHP exec - echo output line by line during progress

I'm trying to find a way in which I can echo out the output of an exec call, and then flush that to the screen while the process is running. I have written a simple PHP script which accepts a file upload and then converts the file if it is not the appropriate file type using FFMPEG. I am doing this on a windows machine. Currently my command looks like so:
$cmd = "ffmpeg.exe -i ..\..\uploads\\".$filename." ..\..\uploads\\".$filename.".m4v 2>&1";
exec( $cmd, $output);
I need something like this:
while( $output ) {
print_r( $output);
ob_flush(); flush();
}
I've read about using ob_flush() and flush() to clear the output buffer, but I only get output once the process has completed. The command works perfectly, It just doesn't update the Page while converting. I'd like to have some output so the person knows what's going on.
I've set the time out
set_time_limit( 10 * 60 ); //5 minute time out
and would be very greatful if someone could put me in the right direction. I've looked at a number of solutions which come close one Stackoverflow, but none seem to have worked.
Since the exec call is a blocking call you have no way of using buffers to get status.
Instead you could redirect the output in the system call to a log file. Let the client query the server for progress update in which case the server could parse the last lines of the log file to get information about current progress and send it back to the client.
exec() is blocking call, and will NOT return control to PHP until the external program has terminated. That means you cannot do anything to dump the output on a line-by-line basis because PHP is suspended while the external app is running.
For what you want, you need to use proc_open, which returns a filehandle you can read from in a loop. e.g.
$fh = proc_open('.....');
while($line = fgets($fh)) {
print($line);
flush();
}
There are two problems with this approach:
The first is that, as #Marc B notes, the fact that exec will block until it's finished. You'll have to devise some way of measuring progress.
The second is that using ob_flush() in this way amounts to holding the connection between server & client open and dribbling the data out a little at a time. This is not something that the HTTP protocol was designed for and while it might work sometimes, it's not going to work consistently - different browsers and different servers will time out differently. The better way to do it is via AJAX calls: using Javascript's setTimeout() function (or setInterval()), make a call to the server periodically and have the server send back a progress report.

PHP script continues after closing / stopping page

Seems php scripts continue even after closing or stopping a page.
for example, if my script is run by
/localhost/test.php
after I close the page, sometimes it continues to run. I'm pretty sure restarting apache clears it out but is there a better way to terminate the php script after it's started.
You could use ignore_user_abort but I think it only applies if you are running PHP as a command line script .
When running scripts from browser , in my experience I have realized scripts running even after the browser was closed . I did not go further to check how long they used to run .
The scripts that does huge processing that could run many minutes , I used to have control as below :
A flag variable is considered which is stored in a database table .
A way is provided to set the flag On or Off ( or 1 or 0 ) .
The flag is checked in the script and the script is stopped running if the flag is found to be Off ( or 0 ) .
One way to consider the flag is in a loop as below :
while(true)
{
/* Your code here which probably does lot of processing */
if($flag === false) break; // Or exit if you prefer
}
If you have code that need to run following the loop , you would use break .If you want to abruptly stop the script you could use exit instead in its place .
check this out: http://php.net/manual/en/function.ignore-user-abort.php
Sets whether a client disconnect should cause a script to be aborted.

PHP auto-kill a script if the HTTP request is cancelled/closed

The problem is that for a long process the PHP script keeps on executing whether or not the client browser is currently connected or not. Is there any possibility that if the client has terminated the Ajax call to a script then the script also terminates on server?
As pointed out by #metadings php does have a function to check for connection abort named connection_aborted(). It will return 1 if connection is terminated otherwise 0.
In a long server side process the user may need to know if the client
is disconnected from the server or he has closed the browser then the
server can safely shutdown the process.
Especially in a case where the application uses php sessions then if we left the long process running even after the client is disconnected then the server will get unresponsive for this session. And any other request from the same client will wait until the earlier process executes completely. The reason for this situation is that the session file is locked when the process is running. You can however intentioanlly call session_write_close() method to unlock it. But this is not feasible in all scenarios, may be one need to write something to session at the end of the process.
Now if we only call connection_aborted() in a loop then it will always
return 0 whether the connection is closed or not.
0 means that the connection is not aborted. It is misleading. However, after re-search and experiments if have discovered that the output buffer in php is the reason.
First of all in order to check for aborts the developer in a loop must send some output to the client by echoing some text. For example:
print " ";
As the process is still running, the output will not be sent to the client. Now to send output we then need to flush the output buffer.
flush ();
ob_flush ();
And then if we check for aborts then it will give correct results.
if (connection_aborted () != 0) {
die();
}
Following is the working example, this will work even if you are using PHP session:
session_start ();
ignore_user_abort ( TRUE );
file_put_contents ( "con-status.txt", "Process started..\n\n" );
for($i = 1; $i <= 15; $i ++) {
print " ";
file_put_contents ( "con-status.txt", "Running process unit $i \n", FILE_APPEND );
sleep ( 1 );
// Send output to client
flush ();
ob_flush ();
// Check for connection abort
if (connection_aborted () != 0) {
file_put_contents ( "con-status.txt", "\nUser terminated the process", FILE_APPEND );
die ();
}
}
file_put_contents ( "con-status.txt", "\nAll units completed.", FILE_APPEND );
EDIT 07-APR-2017
If someone is using Fast-Cgi on Windows then he can actually terminate
the CGI thread from memory when the connection is aborted using following code:
if (connection_aborted () != 0) {
apache_child_terminate();
exit;
}
Check out PHP connection_aborted() function. While doing your processing, you can sometimes check for the aborted connection to gracefully cancel the progress, as one would do in an interactive threading model.
One way I've found which is the easiest way to handle this time-out issue is as such:
1: set a value on the server as 'processing'. Start an independent thread to do the processing.
2: The initial ajax call returns a success
3: The javascript on the page goes into 'waiting mode' which sends a new ajax request every 10 or 30 or 60 seconds or five or ten minutes or whatever (depending on your situation) to find out whether the value on the server is still set to 'processing'.
4: The independent thread completes. It sets the value on the server to 'done'.
5: The javascript on the page makes its next waiting-mode query, and returns 'done' and the appropriate data.
4b: If an obscene amount of time goes by without a 'done', it registers as a failure. How much time is obscene depends upon your situation. Send an ajax call updating the value from 'processing' to 'cancel'.
5b: The independent thread periodically checks the status to make sure it's still set to 'processing'. If it sees a mode-shift to 'cancel' it cancels itself.
This is what you're looking for:
http://php.net/manual/en/function.ignore-user-abort.php
Stopping a script in the middle of execution can lead to unexpected results, so caveat emptor

PHP doesn't detect connection abort at all

I have read and deeply understood these:
http://www.php.net/manual/en/features.connection-handling.php
http://www.php.net/manual/en/function.register-shutdown-function.php
However, I have tested both PHP 5.1.6 and 5.3 and things DON'T work as described there. What I observe is:
connection_status() always return true, even after the client has closed the connection.
execution of the script keeps going on after the client has closed the connection, even though ignore_user_abort is 0
a function registered with register_shutdown_function() is not run until the script reaches ends. The script is NOT interrupted (and hence the function not called) when the client aborts the connection.
So basically PHP just doesn't detect the client's disconnection AT ALL.
Note that this is NOT as if ignore_user_abort was set to 1: if that was the case then connection_status() would return 1 even though the script would keep running and the shutdown function would not be called until the end. That is not the case.
ini_get("ignore_user_abort") returns 0, as expected.
Is this a bug in PHP, or may this be due to some Apache setting?
How do I get PHP to work as described in the abovementioned documentation?
Test script:
<?php
function myShutdown() {
error_log("myShutdown ".connection_status()." ".ini_get("ignore_user_abort"));
}
register_shutdown_function(myShutdown);
echo "Hi!";
error_log(" *** test/test *** ");
for ($i=0; $i<10; $i++) {
sleep(1);
error_log(".");
echo ".";
}
?>
Steps to reproduce:
- visit the url of the script
- abort the connection on the client before 10 seconds have elapsed (e.g. hit the stop button in the browser)
Expected/Desired behavior:
The logs should show less than 10 dots, and at the end "myShutdown 1 0" (if you watch the log in real time, the myShutDown should appear immediately when the client disconnects)
Observed/current behavior:
The logs show always exactly 10 dots, and at the end "myShutdown 0 0" (if you watch it in realtime, it goes on for 10 seconds no matter when the client disconnects).
First, I also failed to get it to work, using the basic ubuntu 12.04 LAMP installation (php5.3). But I've some information and hope that it is helpful. Any comments or edits appreciated! :)
I see two problems with your code. The first is a syntax error. You are missing the single quotes around myShutdown when calling register_shutdown_function(). Change the line to:
register_shutdown_function('myShutdown');
The second problem I see is the missing flush() call after echos. The documentation says:
PHP will not detect that the user has aborted the connection until an attempt is made to send information to the client. Simply using an echo statement does not guarantee that information is sent, see flush().
But even flush() will not help in any case. From the documentation of flush():
flush() may not be able to override the buffering scheme of your web server and it has no effect on any client-side buffering in the browser. It also doesn't affect PHP's userspace output buffering mechanism. This means you will have to call both ob_flush() and flush() to flush the ob output buffers if you are using those.
Several servers, especially on Win32, will still buffer the output from your script until it terminates before transmitting the results to the browser.
Server modules for Apache like mod_gzip may do buffering of their own that will cause flush() to not result in data being sent immediately to the client.
Even the browser may buffer its input before displaying it. Netscape, for example, buffers text until it receives an end-of-line or the beginning of a tag, and it won't render tables until the tag of the outermost table is seen.
Some versions of Microsoft Internet Explorer will only start to display the page after they have received 256 bytes of output, so you may need to send extra whitespace before flushing to get those browsers to display the page.
In the comments of the that page there is an advice to set several headers and apache configs:
apache_setenv('no-gzip', 1);
ini_set('zlib.output_compression', 0);
ini_set('implicit_flush', 1);
however, even this didn't work for me. I've investigated this using wiresharek, although the web server sends content ('Hi') after 0.0037 seconds, the web browser was buffering the page.

PHP connection_aborted() not working correctly

I have the following code:
ignore_user_abort(true);
while(!connection_aborted()) {
// do stuff
}
and according to the PHP documentation, this should run until the connection is closed, but for some reason, it doesn't, instead it keeps running until the script times out. I've looked around online and some recommended adding
echo chr(0);
flush();
into the loop, but that doesn't seem to do anything either. Even worse, if I just leave it as
while(true) {
// do stuff
}
PHP still continues to run the script after the client disconnects. Does anyone know how to get this working? Is there a php.ini setting that I'm missing somewhere?
If it matters, I'm running PHP 5.3.5. Thanks in advance!
I'm a bit late to this party, but I just had this problem and got to the bottom of it. There are multiple things going on here -- a few of them mentioned here:
PHP doesn't detect connection abort at all
The gist of it: In order for connection_aborted() to work, PHP needs to attempt to send data to the client.
Output Buffers
As noted, PHP will not detect the connection is dead until it tries to actually send data to the client. This is not as simple as doing an echo, because echo sends the data to any output buffers that may exist, and PHP will not attempt a real send until those buffers are full enough. I will not go into the details of output buffering, but it's worth mentioning that there can be multiple nested buffers.
At any rate, if you'd like to test connection_abort(), you must first end all buffers:
while (ob_get_level()){ ob_end_clean(); }
Now, anytime you want to test if the connection is aborted, you must attempt to send data to the client:
echo "Something.";
flush();
// returns expected value...
// ... but only if ignore_user_abort is false!
connection_aborted();
Ignore User Abort
This is a very important setting that determines what PHP will do when the above flush() is called, and the user has aborted the connection (eg: hit the STOP button in their browser).
If true, the script will run along merrily. flush() will do essentially nothing.
If false, as is the default setting, execution will immediately stop in the following manner:
If PHP is not already shutting down, it will begin its shutdown
process.
If PHP is already shutting down, it will exit whatever shutdown
function it is in and move on to the next.
Destructors
If you'd like to do stuff when the user aborts the connection, you need to do three things:
Detect the user aborted the connection. This means you have to attempt to flush to the user periodically, as described further above. Clear all output buffers, echo, flush.
a. If ignore_connection_aborted is true, you need to manually test connection_aborted() after each flush.
b. If ignore_connection_aborted is false, a call to flush will cause the shutdown process to begin. You must then be especially careful not to cause flush from within your shutdown functions, or else PHP will immediate cease execution of that function and move on to the next shutdown function.
Putting it all together
Putting this all together, lets make an example that detects the user hitting "STOP" and does stuff.
class DestructTester {
private $fileHandle;
public function __construct($fileHandle){
// fileHandle that we log to
$this->fileHandle = $fileHandle;
// call $this->onShutdown() when PHP is shutting down.
register_shutdown_function(array($this, "onShutdown"));
}
public function onShutdown() {
$isAborted = connection_aborted();
fwrite($this->fileHandle, "PHP is shutting down. isAborted: $isAborted\n");
// NOTE
// If connection_aborted() AND ignore_user_abort = false, PHP will immediately terminate
// this function when it encounters flush. This means your shutdown functions can end
// prematurely if: connection is aborted, ignore_user_abort=false, and you try to flush().
echo "Test.";
flush();
fwrite($this->fileHandle, "This was written after a flush.\n");
}
public function __destruct() {
$isAborted = connection_aborted();
fwrite($this->fileHandle, "DestructTester is getting destructed. isAborted: $isAborted\n");
}
}
// Create a DestructTester
// It'll log to our file on PHP shutdown and __destruct().
$fileHandle = fopen("/path/to/destruct-tester-log.txt", "a+");
fwrite($fileHandle, "---BEGINNING TEST---\n");
$dt = new DestructTester($fileHandle);
// Set this value to see how the logs end up changing
// ignore_user_abort(true);
// Remove any buffers so that PHP attempts to send data on flush();
while (ob_get_level()){
ob_get_contents();
ob_end_clean();
}
// Let's loop for 10 seconds
// If ignore_user_abort=true:
// This will continue to run regardless.
// If ignore_user_abort=false:
// This will immediate terminate when the user disconnects and PHP tries to flush();
// PHP will begin its shutdown process.
// In either case, connection_aborted() should subsequently return "true" after the user
// has disconnected (hit STOP button in browser), AND after PHP has attempted to flush().
$numSleeps = 0;
while ($numSleeps++ < 10) {
$connAbortedStr = connection_aborted() ? "YES" : "NO";
$str = "Slept $numSleeps times. Connection aborted: $connAbortedStr";
echo "$str<br>";
// If ignore_user_abort = false, script will terminate right here.
// Shutdown functions will being.
// Otherwise, script will continue for all 10 loops and then shutdown.
flush();
$connAbortedStr = connection_aborted() ? "YES" : "NO";
fwrite($fileHandle, "flush()'d $numSleeps times. Connection aborted is now: $connAbortedStr\n");
sleep(1);
}
echo "DONE SLEEPING!<br>";
die;
The comments explain everything. You can fiddle with ignore_user_abort and look at the logs to see how this changes things.
I hope this helps anyone having trouble with connection_abort, register_shutdown_function, and __destruct.
Try using ob_flush(); just before flush(); and some browsers just won't update the page before some data is added.
Try doing something like
<? php
// preceding scripts
ignore_user_abort(true);
$i = 0;
while(!connection_aborted())
{ $i++;
echo $i;
echo str_pad('',4096); // yes i know this will increase the overhead but that can be reduced afterwords
ob_flush();
flush();
usleep(30000); // see what happens when u run this on my WAMP this runs perfectly
}
// Ending scripts
?>
Google Chrome has issues with this code, actually; it doesn't support streaming very nicely.
Try:
ignore_user_abort(true);
echo "Testing connection handling";
while (1) {
if (connection_status() != CONNECTION_NORMAL)
break;
sleep(1);
echo "test";
flush();
}
Buffering seems to cause issues depending on your server settings.
I tried disabling the buffer with ob_end_clean but that wasn't enough, I had to send some data to cause the buffer to fully flush out. Here is the final code that ended up working for me.
set_time_limit(0); // run the delay as long as the user stays connected
ignore_user_abort(false);
ob_end_clean();
echo "\n";
while ($delay-- > 0 && !connection_aborted())
{
echo str_repeat("\r", 1000) . "<!--sleep-->\n";
flush();
sleep(1);
}
ob_start();

Categories