Alin Ilici
Alin Ilici

Reputation: 75

Ajax call blocks the server until it finishes

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.

Upvotes: 1

Views: 2070

Answers (4)

user2268997
user2268997

Reputation: 1391

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.

Upvotes: 2

bestprogrammerintheworld
bestprogrammerintheworld

Reputation: 5520

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

Upvotes: -1

Lorenzo Lerate
Lorenzo Lerate

Reputation: 3870

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

Upvotes: 0

WhiteHat
WhiteHat

Reputation: 61222

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

Upvotes: 0

Related Questions