PHP Auto-Kill a Script If the Http Request Is Cancelled/Closed

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

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;
}

Will Ajax be aborted after the request is done and the browser is closed?

When you close your browser or hit the cancel button, the connection to the webserver is terminated. Whether your server-side php script will continue with execution, depends on the PHP/Apache configuration.

Default configuration is to terminate the execution of the PHP script, as soon as the connection is cancelled. However, you can change this default setting to continue execution!

To do this, please use the ignore_user_abort function:

http://php.net/manual/en/function.ignore-user-abort.php

You can read more about the topic here:

http://php.net/manual/en/features.connection-handling.php

There's some more discussion about that here as well:

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

Addition: Just in case, that I am misunderstanding your question: When closing the browser your Javascript code on the client side will of course NOT continue with execution after the PHP script finished.

Terminate PHP script on server through AJAX call

Goto the following link for the exact solution to your problem:

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

Using PHP's __destruct method to log data when a connection is terminated

php does not know and cannot find out by itself whether the user's browser is still running or not.

Efficient and user-friendly way to present slow-loading results

Himel Nag Rana demonstrated how to cancel a pending Ajax request.
Several factors may interfere and delay subsequent requests, as I have discussed earlier in another post.

TL;DR: 1. it is very inconvenient to try to detect the request was cancelled from within the long-running task itself and 2. as a workaround you should close the session (session_write_close()) as early as possible in your long-running task so as to not block subsequent requests.

connection_aborted() cannot be used. This function is supposed to be called periodically during a long task (typically, inside a loop). Unfortunately there is just one single significant, atomic operation in your case: the query to the data back end.

If you applied the procedures advised by Himel Nag Rana and myself, you should now be able to cancel the Ajax request and immediately allow a new requests to proceed. The only concern that remains is that the previous (cancelled) request may keep running in the background for a while (not blocking the user, just wasting resources on the server).

The problem could be rephrased to "how to abort a specific process from the outside".

As Christian Bonato rightfully advised, here is a possible implementation. For the sake of the demonstration I will rely on Symphony's Process component, but you can devise a simpler custom solution if you prefer.

The basic approach is:

  1. Spawn a new process to run the query, save the PID in session. Wait for it to complete, then return the result to the client

  2. If the client aborts, it signals the server to just kill the process.


<?php // query.php

use Symfony\Component\Process\PhpProcess;

session_start();

if(isset($_SESSION['queryPID'])) {
// A query is already running for this session
// As this should never happen, you may want to raise an error instead
// of just silently killing the previous query.
posix_kill($_SESSION['queryPID'], SIGKILL);
unset($_SESSION['queryPID']);
}

$queryString = parseRequest($_POST);

$process = new PhpProcess(sprintf(
'<?php $result = runQuery(%s); echo fetchResult($result);',
$queryString
));
$process->start();

$_SESSION['queryPID'] = $process->getPid();
session_write_close();
$process->wait();

$result = $process->getOutput();
echo formatResponse($result);

?>


<?php // abort.php

session_start();

if(isset($_SESSION['queryPID'])) {

$pid = $_SESSION['queryPID'];
posix_kill($pid, SIGKILL);
unset($pid);
echo "Query $pid has been aborted";

} else {

// there is nothing to abort, send a HTTP error code
header($_SERVER['SERVER_PROTOCOL'] . ' 599 No pending query', true, 599);

}

?>


// javascript
function abortRequest(pendingXHRRequest) {
pendingXHRRequest.abort();
$.ajax({
url: 'abort.php',
success: function() { alert("terminated"); });
});
}

Spawning a process and keeping track of it is genuinely tricky, this is why I advised using existing modules. Integrating just one Symfony component should be relatively easy via Composer: first install Composer, then the Process component (composer require symfony/process).

A manual implementation could look like this (beware, this is untested, incomplete and possibly unstable, but I trust you will get the idea):

<?php // query.php

session_start();

$queryString = parseRequest($_POST); // $queryString should be escaped via escapeshellarg()

$processHandler = popen("/path/to/php-cli/php asyncQuery.php $queryString", 'r');

// fetch the first line of output, PID expected
$pid = fgets($processHandler);
$_SESSION['queryPID'] = $pid;
session_write_close();

// fetch the rest of the output
while($line = fgets($processHandler)) {
echo $line; // or save this line for further processing, e.g. through json_encode()
}
fclose($processHandler);

?>


<?php // asyncQuery.php

// echo the current PID
echo getmypid() . PHP_EOL;

// then execute the query and echo the result
$result = runQuery($argv[1]);
echo fetchResult($result);

?>


Related Topics



Leave a reply



Submit