user3191192
user3191192

Reputation: 179

setTimeout() with recursive call - memory leak?

I am implementing the client side of a long polling system. Everything I read says that to avoid an infinite stack depth do not make a direct recursive call, but call indirectly with setTimeout() so each iteration starts with a clean stack.

The implementation is:

function pollForEvents() {
    
    $.get(
        "GetEventLP", 
        null, 
        function(eventData) {
            console.log("Got event at "+new Date()+": "+JSON.stringify(eventData));
            setTimeout(function() {pollForEvents();}, 1); // Using setTimeout() avoids infinite recursion stack growth
            
        }).fail(function(xhr, status, err) {
            console.log("Long poll failed at "+new Date()+": "+util.getServerErrorText(xhr));
            // Wait before we try again to avoid fast failure polling
            setTimeout(function() { pollForEvents(); }, 60000);
        });
}

I let this run for some number of iterations, then set a breakpoint at the $.get() call. On the next iteration it breaks and Chrome shows a call stack as deep as the number of iterations run - e.g. it grows indefinitely. Am I misunderstanding something? This seems like a memory leak based on the ever increasing call stack size.

Here is the call stack that Chrome shows after 6 iterations, note the repeated pollForEvents() frames.

pollForEvents (index.js?v=1.02:406)
(anonymous) (index.js?v=1.02:411)
setTimeout (async)
(anonymous) (index.js?v=1.02:411)
i (jquery-3.1.1.min.js:2)
fireWith (jquery-3.1.1.min.js:2)
A (jquery-3.1.1.min.js:4)
(anonymous) (jquery-3.1.1.min.js:4)
load (async)
send (jquery-3.1.1.min.js:4)
ajax (jquery-3.1.1.min.js:4)
r.<computed> (jquery-3.1.1.min.js:4)
pollForEvents (index.js?v=1.02:406)
(anonymous) (index.js?v=1.02:411)
setTimeout (async)
(anonymous) (index.js?v=1.02:411)
i (jquery-3.1.1.min.js:2)
fireWith (jquery-3.1.1.min.js:2)
A (jquery-3.1.1.min.js:4)
(anonymous) (jquery-3.1.1.min.js:4)
load (async)
send (jquery-3.1.1.min.js:4)
ajax (jquery-3.1.1.min.js:4)
r.<computed> (jquery-3.1.1.min.js:4)
pollForEvents (index.js?v=1.02:406)
(anonymous) (index.js?v=1.02:411)
setTimeout (async)
(anonymous) (index.js?v=1.02:411)
i (jquery-3.1.1.min.js:2)
fireWith (jquery-3.1.1.min.js:2)
A (jquery-3.1.1.min.js:4)
(anonymous) (jquery-3.1.1.min.js:4)
load (async)
send (jquery-3.1.1.min.js:4)
ajax (jquery-3.1.1.min.js:4)
r.<computed> (jquery-3.1.1.min.js:4)
pollForEvents (index.js?v=1.02:406)
(anonymous) (index.js?v=1.02:411)
setTimeout (async)
(anonymous) (index.js?v=1.02:411)
i (jquery-3.1.1.min.js:2)
fireWith (jquery-3.1.1.min.js:2)
A (jquery-3.1.1.min.js:4)
(anonymous) (jquery-3.1.1.min.js:4)
load (async)
send (jquery-3.1.1.min.js:4)
ajax (jquery-3.1.1.min.js:4)
r.<computed> (jquery-3.1.1.min.js:4)
pollForEvents (index.js?v=1.02:406)
(anonymous) (index.js?v=1.02:411)
setTimeout (async)
(anonymous) (index.js?v=1.02:411)
i (jquery-3.1.1.min.js:2)
fireWith (jquery-3.1.1.min.js:2)
A (jquery-3.1.1.min.js:4)
(anonymous) (jquery-3.1.1.min.js:4)
load (async)
send (jquery-3.1.1.min.js:4)
ajax (jquery-3.1.1.min.js:4)
r.<computed> (jquery-3.1.1.min.js:4)
pollForEvents (index.js?v=1.02:406)
(anonymous) (index.js?v=1.02:411)
setTimeout (async)
(anonymous) (index.js?v=1.02:411)
i (jquery-3.1.1.min.js:2)
fireWith (jquery-3.1.1.min.js:2)
A (jquery-3.1.1.min.js:4)
(anonymous) (jquery-3.1.1.min.js:4)

Upvotes: 1

Views: 584

Answers (1)

user3191192
user3191192

Reputation: 179

I found out what was going on here, maybe this is useful if someone else begins to panic over an apparent call stack memory leak like this. This is not a memory leak, but here is why it appears to be:

The default Chrome developer call stack view has "async stack trace" enabled, which seems to create an artificial view of the stack when async functions are involved (https://developer.chrome.com/docs/devtools/javascript/reference/#call-stack). It is really a view of past call stacks, it is not showing just the current stack.

With this feature enabled, the debugger is holding on to prior async call stacks and showing them all in this view. There is a thin line drawn between the stacks, but it is not obvious what this means. This view is super handy for debugging chains of async calls, but can be deceptive if you think you are looking at a really deep set of nested calls. With this feature disabled, the stack contains only one set of frames as expected - no call stack memory leak.

Upvotes: 2

Related Questions