Home » Php » mysql – Stop PHP script from executing if client disconnects

mysql – Stop PHP script from executing if client disconnects

Posted by: admin July 12, 2020 Leave a comment


I’m having the following problem.

Client from program sends an HTTPS action to server. Server gets the action and all params that are required. But then client disconnects while PHP script that is called is working.

The script echos back the response and the client doesn’t get anything like it shouldn’t. Because it is important action and client should always get the response I want to stop PHP script from executing if this scenario ever happens.

The entire script is in MySQL transaction so if it dies db rollback will occur and it’s essential that it does.

I have put this code at the very end of script


header("Connection: close");



echo $response->asXML();

$size = ob_get_length();

header("Content-Length: $size");



if ( connection_aborted() )
        log('Connection aborted');
        log('Connection is fine');

But connection_aborted always returns 0 (NORMAL CONNECTION) so the script always executes.
connection_status also doesn’t help, so I don’t know what else to try.


I figured it out that this never works only when the script is called with an AJAX request. When it is called with regular HTTP it shows that connection is dead. But when I call it with new XMLHttpRequest() it never figures it out that connection is closed.

It asynchronous request.

So I managed to narrow it down. But I still don’t know how to fix it.

How to&Answers:

After some experimentation, it seems you need to do multiple flushes (writes) to the connection to have PHP detect that the connection has aborted. However it seems you want to write your output as one big chunk of XML data. We can get around the problem of having no more data to send by using the chunked transfer encoding for HTTP requests.


while (ob_get_level()) {

header('Content-Type: text/xml; charset=utf-8');
header('Transfer-Encoding: chunked');
header('Connection: close');

// ----- Do your MySQL processing here. -----

// Sleep so there's some time to abort the connection. Obviously this wouldn't be
// here in production code, but is here to simulate time to process the request.

// Put XML content into $xml. If output buffering is used, dump contents to $xml.
//$xml = $response->asXML();
$xml = '<?xml version="1.0"?><test/>';
$size = strlen($xml);

// If connection is aborted before here, connection_status() will not return 0.

echo dechex($size), "\r\n", $xml, "\r\n"; // Write content chunk.

echo "0\r\n\r\n"; // Write closing chunk, detecting if connection closed.

if (0 !== connection_status()) {
    error_log('Connection aborted.');
    // Rollback MySQL transaction.
} else {
    error_log('Connection successful.');
    // Commit MySQL transaction.

ob_implicit_flush(true) tells PHP to make a flush after each echo statement. The connection status is checked after each chunk is sent with echo. If the connection is closed before the first statement, connection_status() should return a non-zero value.

You may need to use ini_set('zlib.output_compression', 0) at the top of the script to turn off output compression if compression is turned on in php.ini, otherwise it acts as another external buffer and connection_status() may always return 0.

EDIT: The Transfer-Encoding: chunked header modifies the way the browser expects to receive data in the HTTP response. The browser then expects to receive data in as chunks with the form [{Hexadecimal number of bytes in chunk}\r\n{data}\r\n]. So for example, you can send the string “This is an example string.” as a chunk by echoing 1a\r\nThis is an example string.\r\n. The browser would only display the string, not 1a, and keep the connection open waiting until it receives an empty chunk, that is, 0\r\n\r\n.

EDIT: I modified the code in my answer so that it works as a standalone script, rather than rely on $response being defined by the OP’s code. That is, I commented the line with $xml = $response->asXML(); and replaced it with $xml = '<?xml version="1.0"?><test/>'; as a proof of concept.


I think you must not rely on connection_aborted() function: there are some user reports (https://stackoverflow.com/a/23530884/3908097, Alternative to PHP Function connection_aborted()?) that it is not reliable. Also consider situations when network timeout occurs.

I suggest another idea, which by my opinion is more reliable. It’s simple: send XML data to the client and then wait some time for
acknowledge: client sends another HTTP request with confirmation when the data was obtained.
After this confirmation you can commit transaction. In case of timeout – rollback.

This idea requires just minor modifications to server’s php and client’s code.

Modified your php code (test1.php):

header("Connection: close");

echo $response->asXML();

$size = ob_get_length();
header("Content-Length: $size");

// *** added lines ***
$serial = rand();
$memcache_obj = memcache_connect('localhost', 11211);
$memcache_obj->set("test1.$serial", 0);
header("X-test1-serial: $serial");
// ***


// *** changed code ***
$ok = 0;
$tmout = 10*1000; // timeout in milliseconds
// wait until confirmation flag changes to 1 or timeout occurs
while (!($ok = intval($memcache_obj->get("test1.$serial"))))
    if (connection_aborted()) break; // then no wait
    if (($tmout = $tmout - 10) <= 0) break;
    usleep(10000); // wait 10 milliseconds

if ($ok) {
   error_log('User informed');
   // commit transaction
} else {
   // rollback transaction

Code which handles client confirmation (user_ack.php):

header("Content-type: text/plain; charset=utf-8");
$serial = intval($_GET['serial']);

$memcache_obj = memcache_connect('localhost', 11211);
$memcache_obj->replace("test1.$serial", 1);
echo $serial;

And finally client’s code (test1.html) to test:

<!DOCTYPE html>
<meta charset="utf-8"/>
<title>Test connection</title>
<script type="text/javascript">

function user_ack(serial) {
    var httpReq = new XMLHttpRequest();
    httpReq.open("GET", "user_ack.php?serial=" + encodeURIComponent(serial), true);
    httpReq.onreadystatechange = function () {
        if (httpReq.readyState == 4) {
            if (httpReq.status == 200) {
                // confirmation successful
                document.getElementById("ack").textContent = httpReq.responseText;

function get_xml() {
    var httpReq = new XMLHttpRequest();
    httpReq.open("POST", "test1.php", true);
    httpReq.onreadystatechange = function () {
        if (httpReq.readyState == 4) {
            if (httpReq.status == 200) {
                // send confirmation
                var serial = httpReq.getResponseHeader("X-test1-serial");

                // ... process XML ...
                document.getElementById("xml").textContent = httpReq.responseText;
    httpReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    httpReq.send("id=abc"); // parameters to test1.php

<p>XML from server:</p>
<div id="xml"></div>

<p>Confirmation serial: <span id="ack"></span></p>
<p><input type="button" value="XML download test" onclick="get_xml();"/></p>

I use memcached to store value of confirmation flag for communication between test1.php and user_ack.php. This value’s name must by unique for each connection, so I use $serial = rand(); to generate name: "test1.$serial". When created confirmation flag has value 0. After confirmation user_ack.php changes it value to 1.

Because user_ack.php must known flag’s value name, client sends confirmation with $serial value. Value of $serial it gets form server in the custom HTTP header: X-test1-serial when gets XML.

I use memcache for simplicity. Also MySQL table can by used for this purpose.


PHP can detect the connection is aborted only when it tries to send some output to browser. Try to output some whitespace characters, flush() output, then test connection_status(). You might have to output enough characters to get the buffer through, in my case 64 kb worked.

echo str_repeat(' ', 1024*64);

if ( connection_status() != 0 )

Notice I added ignore_user_abort(true), without it, the execution will end at script output, that is at echo or flush().