8

Let's say, for example, you are writing a program that waits for a message on a queue, handles it, and then waits for the next message, and this goes on forever. In a language like C or Java it would look something like this:

void processMessage() {
    while (true) {
        // waitForMessage blocks until the next message is received
       msg = waitForMessage(); 
      // handle msg here
    }
}

In Javascript (I'm using node.js, btw), because callbacks are used, it normally looks like this:

function processMessage() {
    waitForMessage(function(msg) {
        // handle msg or error here
        processMessage();
    }); 
}

My worry is that you basically have a chain of callbacks that recursively call the original function and the overhead of doing so might slowly eat up memory. I'm guessing this isn't actually a problem since maybe javascript callbacks exist on their own stack independently and are not pushed on to the stack of the original functions? Someone explain javascript callbacks and scoping to me and assure me that the javascript code will not run out of memory when run for an arbitrarily long amount of time while receiving an arbitrarily large number of messages

Mike
  • 1,727
  • 2
  • 19
  • 32
  • 2
    That's not really *recursive*, since each callback is invoked on a fresh stack when the triggering asynchronous event happens. In other words, the first call to `processMessage()` will be long complete when the next one happens in response to the event. – Pointy May 15 '15 at 16:01
  • you would be able to see this in chrome inspector if you setup a situation where this should run multiple times. My guess is that there shouldnt be an issue as long as you complete the processes on each callback – BillPull May 15 '15 at 16:01

3 Answers3

6

No, recursive function calls do not cause memory leaks in Javascript.

The only memory used by a function call is a bit of stack space (so the interpreter knows where to go when the function returns) and whatever memory is used by the scope object of the function (e.g. the local variables). That stack memory is completed returned to the system when the function call returns. It does not leak.

In JavaScript, with asynchronous callbacks, the initiating function has already returned and thus the stack has been cleared long before the asynchronous callback has been called so there is no build up of the stack.

There will be a function scope object in memory until the callback is done and that is important and is required to allow the inline callback to have access to the variables declared in its parent scope. As soon as the callback is done (not reachable any more), that scope object will be garbage collected. Unless you are doing something unusual such as allocated giant strings or buffers in that temporary scope, the memory usage of that scope should not be an issue.

As for retrieving many messages from one initial function call and then repeated calls of the same callback, keep in mind that the parent function is just executed once and just one scope object is allocated no matter how many times the callback is called so there is no build up of memory for each time the callback is called. The callback itself will get a new function scope each time it is called, but since the callback itself doesn't trigger any asynchronous calls, that scope object will be temporary and will be eligible for garbage collection as soon as the callback is done with its work and returns.

If you chain/embed asynchronous operations inside each other, then additional scope objects will be retained for the duration of the asynchronous operation, but this is how Javascript works and is what offers the features of having access to your parent scope. In practice, it has not generally proven to be a memory issue. Scope objects by themselves are relatively compact objects (one is created for nearly every function call) so as I said above as long as you don't put giant buffers or giant strings/arrays into a persisting scope, the memory usage is usually not relevant.


Also keep in mind that when you call processMessage() again from within the asynchronous callback that isn't the kind of recursion that you might generally think of because the previous function call to processMessage() has already returned and the stack has completely unwound before the asynchronous event triggered the callback. So, there is no stack build-up in this case. This is because asynchronous operations in Javascript all run through an event queue. When an asynchronous operation is ready to trigger an action, it puts an event in the Javascript event queue. That event is only processed when the current thread of JS operation has finished and completely unwound. Only then does the JS interpeter look in the event queue to see if there is something else to do. As such, the stack is always completely unwound before the next asynchronous operation is triggered.

For more info on how this works and a number of reference articles on the JS event queue (which works the same in node.js that it does in the browser), see this article:

How does JavaScript handle AJAX responses in the background?

This is one of the reasons that Joyent calls node.js an "an event-driven, non-blocking I/O model" right on the node.js home page.

Community
  • 1
  • 1
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • You first say there is no memory leak, and then say "The only memory used by a function call is a bit of stack space...". Being a small amount of memory doesn't make it less of a memory leak. I'm pretty sure that if you infinitely call a function recursively you'll get a memory overflow eventually. – icenac Sep 16 '16 at 18:13
  • function a(){var b=[100]; for(var i=0; i<100; i++){b[i]=i + " " + i + " " + i;} console.log('a'); setTimeout(a, 10);}; Here, try this in your console and see how the memory usage is increasing in time. – icenac Sep 16 '16 at 18:14
  • @icenac - Stack space is not a memory **leak**, it's memory usage. It's just memory used until the function returns. Calling a synchronous function infinitely recursively will run into a stack overflow fairly quickly. That is a completely different issue from a memory leak. Something to be avoided, but a different class of issue than memory leaks. – jfriend00 Sep 16 '16 at 18:15
  • In my example, the array is allocated on the heap for sure. And as the scope is not exited, the heap usage will increase over time. It is not allocated on the stack for multiple reasons. 1) This is a script, not an executable, so only the browser process has a real stack, not the scripts running in it. 2) If I allocate an array of 100M bytes, it cannot be allocated on the stack anyway, as the stack is much more limited in the majority of modern operating systems. – icenac Sep 16 '16 at 18:21
  • @icenac - I don't get what your point is. What part of my answer to the question asked here do you think is incorrect or needs clarification. If your example is consuming larger and larger amounts of memory, then that is probably just because you aren't allowing enough time for garbage collection to happen or you aren't looking at memory actually in use versus process memory. Your local variable `b` has no lasting reference so it's eligible for garbage collection and should be GCed when GC runs. And, the scope IS exited. `setTimeout()` is non-blocking. – jfriend00 Sep 16 '16 at 18:24
  • "No, recursive function calls do not cause memory leaks in Javascript." - I do not agree with this part. Yes the b variable is eligible for garbage collection in my specific example. But in a real-world example someone would have used that array, and then the GC would have had no clue of whether or not the space could be freed. And as the scope is never existed, I can't see how this is not a leak. – icenac Sep 16 '16 at 18:31
  • @icenac - You are confused about the scope being exited. Right after `setTImeout(a, 10)` is called, the scope is exited. `setTimeout()` is non-blocking. It runs, schedules the next call to `a()` to run at an appropriate time in the future and returns immediately. Then the function `a()` returns and the scope is exited. There is no code running inside that scope any more than has any access to any of the variables in the scope. As such, everything in that scope can be garbage collected (when the GC next runs). Then ~10ms later a new call to `a()` gets initiated. – jfriend00 Sep 16 '16 at 18:35
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/123559/discussion-between-icenac-and-jfriend00). – icenac Sep 16 '16 at 18:36
3

Functions aren't allocated on the stack. The callback function will be garbage collected after it's been used unless a reference is kept for some reason. Your code should be just fine!

ReyCharles
  • 1,772
  • 10
  • 30
  • 1
    Function calls are put on the stack as a means of keeping track of where execution should go when the function returns. Function scope objects are not put on the stack because their lifetime can be different than the execution of the function itself so they are garbage collected like other objects in Javascript. – jfriend00 May 15 '15 at 16:26
0

here is great description abut nextTick()

function processMessage() {
    waitForMessage(function(msg) {
        // handle msg or error here
        process.nextTick(processMessage;
    }); 
}
PPB
  • 2,937
  • 3
  • 17
  • 12