1

I have a forum (in PHP with Symfony2 framework) and I want something really close to real time notifications. What I want more specifically is that when a user receives a private message, a notification appears. I've tried to use the long polling technique, but I have a problem.

So, I send an ajax call from javascrip to php. PHP receives it and makes a query to database to see if there are any new notifications for the user requested by the ajax. If it finds something, it returns that result, else sleeps x seconds. This is happening in a while(true) condition. The problem is that if it doesn't find something, it sleeps, then searches again, and again it sleeps and so on, and while this happens nothing else works. If I try to reload the page, click on something from the forum, read a topic, and so on, all are blocked waiting for that ajax call to finish. What can I do to make that ajax call run in parallel with everything else, or unblock the server while the ajax isn't finished.

This is what I have in javascript:

function poll() {
    $.ajax({
        type: "post",
        url: url to the action from controller,
        async: true,
        data: { 'userId': $('#userId').attr('data-value') },
        dataType: "json",
        success: function(result) {
            call method to display the notification(s) to the user
            setTimeout(poll, 5000);
        }
    });
};

$(document).ready(function(){
    poll();
});

And here is the action from Symfony2 controller:

public function checkForNewNotificationsAction($userId) {
    while (true) {
        /** @var \Forum\CoreBundle\Entity\Notification[] $notifications */
        $notifications = query the database

        if ($notifications != null) {
            return new JsonResponse($notifications);
        } else {
            sleep(5);
        }
    }
}

I have also tried to call a regular PHP file with "pure" PHP code thinking that the action from controller and how Symfony2 manages the controllers is blocking my other activities, but it does exactly the same.

I'm trying to not use any other "tools" like Node.js with Socket.io or something like this because I never worked with them and really don't have time to study. The project must be done in a few days, and still have a lot to do. Also I'm trying to avoid simple ajax calls every x seconds, leaving this as my last option.

Alin Ilici
  • 75
  • 6

4 Answers4

2

This is natural because the script is acquiring a lock on the session.
Add a session_write_close(); to the beginning of the Ajax action so the lock is released.What you're basically doing is tell php, you're not gonna write into the session anymore so release the lock, this way concurrent requests can be handled.Be careful not to write into the session after making this call. Official source: session_write_close()

Update:

symfony's SessionInterface has a save method, which in turn calls session_write_close(), you should use that instead of session_write_close.e.g:

$request->getSession()->save();

When you want to write to the session again, you should call SessionInterface::start.

user2268997
  • 1,263
  • 2
  • 14
  • 35
  • I've already tried that. Put session_write_close() the first line after my function header, the first line in the while(true) condition, the first line in if or the first line on else. Nothing works, it does exactly the same. – Alin Ilici Aug 16 '15 at 15:40
  • That's strange cause this is a common issue, and this is AFIK always the solution.check these out: http://konrness.com/php5/how-to-prevent-blocking-php-requests/ , http://stackoverflow.com/questions/12401358/why-should-session-write-close-be-used-in-long-polling. Do you think there could be anything else acquiring some kind of lock on something? – user2268997 Aug 17 '15 at 03:19
  • All I can think of is that Symfony is somehow doing something more when managing controller actions and probably this is causing my problem (or could be that session_start() which I didn't try). I'll try with session_write_close() in that "pure" PHP script and let you know what happened. But I'll try this tonight, now I don't have access to that project. – Alin Ilici Aug 17 '15 at 07:37
  • OK.Just FYI I am using this exact approach with symfony 2.5 to display a progressbar. – user2268997 Aug 17 '15 at 08:03
  • I use Symfony 2.7.3, but now if you say it works for you, it's most likely not Symfony's fault. Do you start the session manually with session_start() and then close it when you want, or just close it letting the session start automatically? – Alin Ilici Aug 17 '15 at 08:10
  • I really want to try this, it seems very promising. Let you know tonight if it works or not. – Alin Ilici Aug 17 '15 at 08:50
  • Unfortunately this isn't working either. I don't know what else to do, seems nothing works for me. – Alin Ilici Aug 17 '15 at 17:18
  • Can it be from any apache server configuration which doesn't allow concurrent requests? Or any php config file, or something like this? – Alin Ilici Aug 17 '15 at 17:25
  • don't know, but try it with the php server.`app/console server:run` – user2268997 Aug 18 '15 at 03:08
0

Try this.

var myvar = setInterval(function () {checkForNewNotificationsAction($userId)}, 5);

public function checkForNewNotificationsAction($userId) {
   while (true) {
    /** @var \Forum\CoreBundle\Entity\Notification[] $notifications */
    $notifications = query the database

    if ($notifications != null) {
        clearInterval(myVar);
        return new JsonResponse($notifications);
    }
  } 
}
Lorenzo Lerate
  • 3,552
  • 3
  • 26
  • 25
  • It will sleep over and over again until a notification appears, which is exactly what I want. If I return something it will respond to ajax, and I don't want this. I want as fewer ajax calls, letting the server respond only when it really has something to offer to the client. – Alin Ilici Aug 15 '15 at 22:00
  • I've modified it. Is it that you want or something similar? You have to put un $userId the parameter you want. Look at this page for more info: http://www.w3schools.com/js/js_timing.asp – Lorenzo Lerate Aug 16 '15 at 08:50
0

what if you let the PHP return immediately and change the JavaScript to something like this? At least you could do stuff.

function poll() {
    $.ajax({
        type: "post",
        url: url to the action from controller,
        async: true,
        data: { 'userId': $('#userId').attr('data-value') },
        dataType: "json",
        success: function(result) {
            //call method to display the notification(s) to the user
            //if (problem) {pollForever = false;}
        }
    });
};

var pollForever;

$(document).ready(function(){
    pollForever = true;

    while (pollForever) {
        setTimeout(poll, 5000);
    }
});
WhiteHat
  • 59,912
  • 7
  • 51
  • 133
  • As I said, this is my last option. I don't want the client to constantly check for notifications, I want the server to do that by itself and only respond when it has something new. – Alin Ilici Aug 15 '15 at 22:03
-1

A "qucik and dirty way" of "solving" this:

Instead of actually doing a request to the database in your PHP-code, you could do something like this:

  • Create a file called dbchanged.txt with content "0"
  • When you change something in your database, you change the content of that file "1".
  • When you do the request from ajax, you check the content of dbchanged.txt
    • If the dbchanged.txt has content "1", then do the db-request. When this request is done, change the dbchanged.txt content to "0" and then return.
    • If the dbchanged.txt has content" 0", then just return without doing any db-request.

Why I say "solving" this is that it doesn't actually solve the problem, but it would make it far quicker because it's (usually) quciker to read a file than access a database. On the other hand you might have to face issues with file-locking - depending on how many request we are talking about.

Do it the right way!

There is no "quick way" of solving your problem and I would really recommend you to read up on long pulliing request and websockets. It really doesn't seem that hard using sockets.IO and is a way better solution than above - Here's how to create a chat-application (and its kind of simple to understand what's going in the code): http://socket.io/get-started/chat/

HTML5 websockets isn't available in all browsers, but socketIO (that uses html5 websockets) takes care of that for you and uses long pulling-technique instead when websockets is not available.

Example with jquery and socketIO: https://gist.github.com/anandgeorge/2814934

Another reference that might help: http://techoctave.com/c7/posts/60-simple-long-polling-example-with-javascript-and-jquery

bestprogrammerintheworld
  • 5,417
  • 7
  • 43
  • 72