Multiple AJAX requests delay each other
Asked Answered
M

3

13

I have a long polling request on my page. The script on the server side is set to timeout after 20 seconds.

So, when the long polling is "idling", and the user presses another button, the sending of that new request is delayed until the previous script times out.

I can't see anything wrong with the code on jQuery side. Why is the onclick-event delayed?

function poll()
{
$.ajax({
    url: "/xhr/poll/1",
    data: {
        user_id: app.user.id
    },
    type: "POST",
    dataType: "JSON",
    success: pollComplete,
    error: function(response) {
        console.log(response);
    }
});
}

function pollComplete()
{
    poll();
}

function joinRoom(user_id)
{
$.ajax({
    url: "/xhr/room/join",
    dataType: "JSON",
    type: "POST",
    data: {
        user_id: app.user.id,
        room_id: room.id
    }
});
}

<button id="join" onclick="javascript:joinRoom(2);">Join</button>

############ PHP Controller on /xhr/poll

$time = time();
while ((time() - $time) < 20)
{
    $updates = $db->getNewStuff();

    foreach ($updates->getResult() as $update)
        $response[] = $update->getResponse();

    if (!empty($response))
        return $response;
    else
        usleep(1 * 1000000);

    return 'no-updates';
}

Could the "usleep" be the problem?

XHR Screenshot

Monstrous answered 1/8, 2011 at 19:25 Comment(2)
is the problem is there in localhost too??Extern
Does the PHP code for either AJAX call utilize sessions?Whitener
W
27

If you use sessions in the AJAX handling functions, you may run in to an issue where the server is maintaining session data on disk. If so, the data may be locked by the first request, so each subsequent request ends up waiting for the session data file to be available before it proceeds. In effect, this makes asynchronous calls block one another, you end up with linear responses to the requests in chronological order - synchronous. (here's a reference article)

A PHP-specific solution is to use session_write_close (docs) to close out the session as soon as you don't need it any more. This allows other subsequent requests to proceed because the session data will be "unlocked". Other server-side languages manage sessions in different ways, but this is usually something you can manage or control through some mechanism.

Managing sessions can have some pitfalls. If you call session_write_close (or otherwise end a session) right before you return a response, then you aren't going to do yourself any favors because the session would have been unlocked as soon as the response was sent. Thus, it needs to be called as early as possible.In smaller projects, this isn't so bad because you often have a php script that just handles the request and dumps a response, but if you have a larger framework and your request handler is only a part of it, you'll have to explore a more top-level solution to non-blocking session usage so your subcomponents are not closing a session that the framework expects is still open.

One route is to go with database session. There are pros and cons to this solution which are beyond the scope of this answer - check Google for exhaustive discussion for your particular server-side language. Another route is to use a function that opens a session, adds a variable, then closes it. You risk race conditions with this solution, but here's a rough outline using PHP as an example:

function get_session_var($key, $default=null) {
    if (strlen($key) < 1)
        return null;
    if (!isset($_SESSION) || !is_array($_SESSION)) {
        session_start();
        session_write_close();
    }
    if (array_key_exists($key, $_SESSION))
        return $_SESSION[$key];
    return $default;
}
function set_session_var($key, $value=null) {
    if (strlen($key) < 1)
        return false;
    if ($value === null && array_key_exists($key, $_SESSION)) {
        session_start();
        unset($_SESSION[$key]);
    } elseif ($value != null) {
        session_start();
        $_SESSION[$key] = $value;
    } else {
        return false;
    }
    session_write_close();
    return true;
}
Whitener answered 3/8, 2011 at 21:7 Comment(1)
I didn't exactly used your proposed solution, but the hint with session_write_close() was perfect! My requests were using sessions, and disabling them worked perfectly fine.Monstrous
O
1

This sounds consistent with the 2 request rule - browsers only allow two concurrent connections to the same host at any one given time. That having been said, you should be fine with a long poll (receive) and send channel. Are you starting the long poll after page load using $(function(){...? Are you sure the request is being delayed on the client, and not in the browser? What are you seeing in firebug?

Odessa answered 1/8, 2011 at 20:47 Comment(4)
Yes, I am sure, because the request doesn't even show up in firebug for seconds. But when the polling ends and starts from the beginning, the request shows up, just like in the screenshot above.Monstrous
I tested the AJAX alone, without the polling, and it took around 800ms to complete. When the polling is running, it always takes more than 2 seconds.Monstrous
I agree here. Although newer browsers allow more (FF allows 6 and IE 8 allows 8 I guess).Mendelson
As has been mentioned (sort of), this issue is mainly related to IE 8 and below (weblogs.asp.net/mschwarz/archive/2008/07/21/…) As mentioned in the link, however, the HTTP spec suggests that clients should limit the number of concurrent connections. This is why a CDN is suggested - the CDN is a different domain name and thus allows you to bypass the concurrency restrictions of user agents. All that said, I don't think that is entirely the OP's problem :)Whitener
E
1

One thing you can do, you can abort the running poll and run your request first and then again start the poll.

//make sure pollJqXhr.abort is not undefined
var pollJqXhr={abort:$.noop}; 

function poll()
{
    //assign actual jqXhr object
    pollJqXhr=jQuery.ajax({youroptions});
}

function pollComplete()
{
   poll();
}


function joinRoom(user_id)
{
   //pause polling
   pollJqXhr.abort();

   jquery.ajax({
           /*options here*/
           success:function()
           {
                /*Your codes*/

                //restart poll
                poll()
           }
    });
}
Extern answered 3/8, 2011 at 20:55 Comment(1)
Seems like this is treating the symptom rather than the causeWhitener

© 2022 - 2024 — McMap. All rights reserved.