PHP Event Source keeps executing

I started using push in HTML5 using the JavaScript EventSource object. I was totally happy with a working solution in PHP:

$time = 0;
while(true) {
    if(connection_status() != CONNECTION_NORMAL) {
        mysql_close()
        break;
    }
    $result = mysql_query("SELECT `id` FROM `table` WHERE UNIX_TIMESTAMP(`lastUpdate`) > '".$time."'");
    while($row = mysql_fetch_array($result)) {
        echo "data:".$row["id"].PHP_EOL;
        echo PHP_EOL;
        ob_flush();
        flush();
    }
    $time = time();
    sleep(1);
}

But suddenly my WebApp wasn’t reachable anymore with an MySQL error “too many connections”.

It turned out that the MySQL connection doesn’t close after closing the event source in JavaScript:

window.onload = function() {
    sse = new EventSource("push.php");
    sse.onmessage = function(event) {
        console.log(event.data.split(":")[1]);
    }
}
window.onbeforeunload = function() {
    sse.close();
}

So I guess that the PHP script does not stop to execute. Is there any way to call in function (like die();) before the clients connection disconnects? Why doesn’t my script terminate after calling .close(); on the EventSource?!

Thanks for help! —

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

First off: when you wrap your code in a huge while(true) loop, your script will never terminate. The connection with the DB would have been closed when your script “ran out of code to execute”, but since you’ve written a deadlock, that’s not going to happen… ever.
It’s not an EventSource issue, that merely does what it’s supposed to do. It honestly, truly, and sadly is your fault.

The thing is: a user connects to your site, and the EventSource object is instantiated. A connection to the server is established and a request for the return value of push.php is requested. Your server does as requested, and runs the script, that -again- is nothing but a deadlock. There are no errors, so it can just keep on running, for as long as the php.ini allows it to run. The .close() method does cancel the stream of output (or rather it should), but your script is so busy either performing its infinite loop, or sleeping. Assuming the script to be stopped because a client disconnects is like assuming that any client could interfere with what the server does. Security-wise this would be the worst that could happen. Now, to answer your actual question: What causes the issue, how to fix it?
The answer: HEADERS

Look at this little PHP example: the script isn’t kept alive server-side (by infinite loops), but by setting the correct headers.

Just as a side-note: Please, ditch mysql_* ASAP, it’s being deprecated (finally), use PDO or mysqli_* istead. It’s not that hard, honestly.

Solution 2

I had exactly the same issue and as far as I understood it the reason was that apache didn’t terminate the php script until I tried to write data trough the closed socket connection again.

I didn’t have any changes in my database so there was no output.

To fix this I simply echo a event source comment in my while loop like:

echo ": heartbeat\n\n";
ob_flush();
flush();

This causes the script to be terminated if the socket connection is closed.

Solution 3

you might want to try to add this to the loop:

if(connection_status() != CONNECTION_NORMAL)
{
    break;
}

but php should stop when the client disconnected.

Solution 4

1.

window.onbeforeunload = function() {
    sse.close();
}

this is not required, EventSource will be closed at page unloading time.

  1. even if you can not find a way to detect disconnected client on PHP, you can just stop execution after N seconds, EventSource will reconnect automatically.

http://www.php.net/manual/ru/function.connection-aborted.php
comments on this page says, that u should use “flush” before connection_status
anyway, you should drop connection after N seconds, because u cant detect “bad” disconnects.

3.
echo “retry:1000\n”; // use this to tell EventSource the reconnection delay in ms

Solution 5

$time = 0;
while(true) { 
    if(connection_status() != CONNECTION_NORMAL) {
        mysql_close()
        break;
    }
    $result = mysql_query("SELECT id FROM table WHERE UNIX_TIMESTAMP(lastUpdate) > '".$time."'");
    while($row = mysql_fetch_array($result)) {
        $rect[] = $row;
    }
    for($i-0;$i echo "data:".implode('',$disp)."\n\n"; $time = time(); sleep(1);
}

Solution 6

As Elias Van Ootegem pointed out, your script never terminates and hence you have a bunch of MySQL connections active. More concerningly, you have a bunch of resources being used in the form of PHP loops running with no end in sight!

Unfortunately, the solution to keeping the is not "headers". In the example at this link referenced by Elias, the php script terminates, and the client javascript actually has to re-open a connection. You can test this by using the following code

source.addEventListener('open', function(e) {
  // Connection was opened.
    console.log("Opening new connection");
}, false);

If you follow his example on github, in implementation the author actually employs a loop that terminates after X seconds. See the do loop and comments at bottom of the script.

The solution is to give your loop a lifespan.

By defining a limit to the loop, IE X itterations or after X amount of time to trigger the end of the loop or die();, you will make it so that if the client disconnects there is a limit to how long a script will remain active. If the client is still there after X amount of time, they will simply re connect.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply