3

I am developing online chat between users on my website. Chat is working properly, but nobody can enter the website if long poll connection is open.

My code [updated]:

$time = $_GET['time'];
while (true) {
    $sth = $db->prepare("SELECT * FROM messages LEFT JOIN users ON users.username=messages.chat_msg_user_id WHERE chat_msg_client_id=:chat_msg_client_id AND chat_msg_id>:chat_msg_id AND chat_notification='0' ORDER BY chat_msg_time DESC LIMIT 1");
    $sth->execute([":chat_msg_client_id" => $client_id, ":chat_msg_id" => $last_message_id]);
    $messages = [];
    while ($answer = $sth->fetch()) {
        $msg = ["chat_msg_id" => $answer["chat_msg_id"], "chat_msg_user_id" => $answer["chat_msg_user_id"], "chat_username" => $answer['username'], "chat_user_photo" => $answer['mainphoto'], "chat_user_status" => $answer['status'], "chat_user_name" => $answer["name"], "chat_msg_from" => $answer['chat_msg_from'], "chat_msg_time" => date("H:i", $answer["chat_msg_time"]), "chat_msg_date" => date("m.d.y", $answer["chat_msg_time"]), "chat_msg_text" => mb_str_replace("\n", "<br>", $answer["chat_msg_text"]), "read" => $answer['chat_read'], ];
        $messages[] = $msg;
        $last_message_id = $answer["chat_msg_id"];

        // some variables here for json_encode below //

    }
    if (count($messages) > 0) {
        $sth2 = $db->prepare("SELECT count(chat_read) as unread_messages_count FROM messages WHERE chat_msg_client_id='$client_id' AND chat_read='0'");
        $sth2->execute();
        $answers = $sth2->fetch();
        $unread_messages_count = $answers['unread_messages_count'];
        echo json_encode(["command" => "new_messages", "messages" => $messages, "last_message_id" => $last_message_id, "chat_msg_id" => $chat_msg_id, "chat_user_name" => $chat_user_name, "chat_user_status" => $chat_user_status, "chat_user_photo" => $chat_user_photo, "chat_msg_from" => $chat_msg_from, "chat_msg_time" => $chat_msg_time, "chat_msg_date" => $chat_msg_date, "chat_msg_text" => $chat_msg_text, "unread_messages_count" => $unread_messages_count, ]);
        exit();
    }
    usleep(10000);
    if ((time() - $time) > 60) {
        echo json_encode(["command" => "timeout"]);
        exit();
    }
 }

UPDATE 2: My hosting provider sent some information about it, but I can not understand what is it...:

sendto(3, "\306\0\0\0\3SELECT * FROM messages LEFT JOIN users ON users.username=messages.chat_msg_user_id WHERE chat_msg_client_id='222' AND chat_msg_id>'571' AND chat_notification='0' ORDER BY chat_msg_time DESC LIMIT 1", 202, MSG_DONTWAIT, NULL, 0) = 202

UPDATE 3: I forgot to say — I have 2 long poll connections per user. One for fetching new chats and new messages (for notifications) and another one for fetching messages while chatting.

Mark
  • 43
  • 5

1 Answers1

1

PHP usually uses a threadpool to process requests. Putting execution to sleep is not really supported by this approach. You end up exhausting the available threads pretty quickly, and new requests won't get processed because all threads are sleeping.

You either need to increase the number of threads (which won't scale and may not be supported by your hosting provider), or switch to a different approach (by using a language/framework that supports asynchronous request processing, e.g. NodeJS).

See also this excerpt from an answer regarding long polling in PHP:

Note: With a real site, running this on a regular web-server like Apache will quickly tie up all the "worker threads" and leave it unable to respond to other requests.. There are ways around this, but it is recommended to write a "long-poll server" in something like Python's twisted, which does not rely on one thread per request. cometD is an popular one (which is available in several languages), and Tornado is a new framework made specifically for such tasks (it was built for FriendFeed's long-polling code)... but as a simple example, Apache is more than adequate! This script could easily be written in any language (I chose Apache/PHP as they are very common, and I happened to be running them locally)


Update:

My hosting provider said that Apache has too many connections to database and my server dieing cause of it

If this is the problem, then it might be fixable (until the other issue regarding PHP threads takes over) by closing the database connections before entering sleep, see this for PDO.

Yogu
  • 9,165
  • 5
  • 37
  • 58
  • Thank you for reply! My hosting provider said that Apache has too many connections to database and my server dieing cause of it. As I posted in "UPDATE 2". I have another website with same online chat (but only with 1 long poll connection (only for fetching messages)) and chat is working propertly, server working properly too. – Mark Nov 04 '18 at 17:02
  • If that's the problem, it might be fixable (for now), I updated the answer accordingly. (And I was wrong about the Update 2 in this case.) – Yogu Nov 04 '18 at 17:09
  • So in my situation $sth = null; ($sth = $db->close(); not working) will close the connection? – Mark Nov 04 '18 at 17:19
  • I think you need to set all connection-related variables to `null` - at least `$db` and `$sth`. – Yogu Nov 04 '18 at 17:21
  • is_resource($db) says that connection is closed without setting variables to null. – Mark Nov 04 '18 at 17:39
  • I think `$db` would yield `false` for `is_resource` just because it's an object and not a resource. It does not really say anything about the connection status. – Yogu Nov 04 '18 at 18:04
  • When I set $db = null, I recieved an error: Fatal error: Call to a member function prepare() on null. Seems like connection is closed now and it didn't open on next poll – Mark Nov 04 '18 at 20:05
  • Yes, you need to reinitialize the connection (the `$db` object) after the sleep call, just like you did initially. – Yogu Nov 04 '18 at 21:09