Node.js - maximum call stack size exceeded

When I run my code, the Node.js throw exception "RangeError: Maximum call stack size exceeded" caused by too many recursion calls. I tried to increase the stack size of Node.js on sudo node --stack-size=16000 app , but Node.js crashed without any error message. When I ran this again without sudo, then Node.js print 'Segmentation fault: 11' . Is there any way to solve this problem without removing the recursive call?

thank

+66
callstack stack-overflow recursion
Jan 05 '14 at 17:08
source share
8 answers

You have to transfer your recursive function call to

  • setTimeout ,
  • setImmediate or
  • process.nextTick

to give node.js a chance to clear the stack. If you do not and there are many loops without any real function call aync, or if you do not RangeError: Maximum call stack size exceeded for the callback, your RangeError: Maximum call stack size exceeded will be inevitable.

There are many articles on the "Asynchronous Type Potential Loop". Here is one of them .

Now another example code:

 // ANTI-PATTERN // THIS WILL CRASH var condition = false, // potential means "maybe never" max = 1000000; function potAsyncLoop( i, resume ) { if( i < max ) { if( condition ) { someAsyncFunc( function( err, result ) { potAsyncLoop( i+1, callback ); }); } else { // this will crash after some rounds with // "stack exceed", because control is never given back // to the browser // -> no GC and browser "dead" ... "VERY BAD" potAsyncLoop( i+1, resume ); } } else { resume(); } } potAsyncLoop( 0, function() { // code after the loop ... }); 

It is right:

 var condition = false, // potential means "maybe never" max = 1000000; function potAsyncLoop( i, resume ) { if( i < max ) { if( condition ) { someAsyncFunc( function( err, result ) { potAsyncLoop( i+1, callback ); }); } else { // Now the browser gets the chance to clear the stack // after every round by getting the control back. // Afterwards the loop continues setTimeout( function() { potAsyncLoop( i+1, resume ); }, 0 ); } } else { resume(); } } potAsyncLoop( 0, function() { // code after the loop ... }); 

Now your loop may become too slow because we lose a little time (one browser round trip) per round. But you do not need to call setTimeout in each round. Usually it's ok to do this every 1000 times. But this may vary depending on the size of your stack:

 var condition = false, // potential means "maybe never" max = 1000000; function potAsyncLoop( i, resume ) { if( i < max ) { if( condition ) { someAsyncFunc( function( err, result ) { potAsyncLoop( i+1, callback ); }); } else { if( i % 1000 === 0 ) { setTimeout( function() { potAsyncLoop( i+1, resume ); }, 0 ); } else { potAsyncLoop( i+1, resume ); } } } else { resume(); } } potAsyncLoop( 0, function() { // code after the loop ... }); 
+90
Jan 08 '14 at
source share

I found a dirty solution:

 /bin/bash -c "ulimit -s 65500; exec /usr/local/bin/node --stack-size=65500 /path/to/app.js" 

It just increases the call stack limit. I think this is not suitable for production code, but I need it for a script that runs only once.

+20
Jan 05 '14 at 22:32
source share

In some languages, this can be solved by tail call optimization, where the recursion call is converted under the hood into a loop, so the maximum stack size does not exist.

But in javascript current engines do not support this, it anticipates a new version of Ecmascript 6 .

Node.js has several flags to enable ES6 features, but the tail call is not yet available.

So, you can reorganize your code to implement trampolining or refactoring to convert recursion into a loop .

+6
Jan 05 '14 at 17:29
source share

I had a similar problem like this. I had a problem using multiple Array.map () in a row (about 8 cards at a time), and I was getting the maximum_call_stack_exceeded error. I solved this by changing the map to "for" loops

So if you use a lot of map calls, changing them to a for loop can solve the problem

edit

Just for clarity and probably not necessary information, but useful for information, using .map() calls for preliminary preparation of the array (resolving recipients, etc.) and caching the callback, as well as internally saves the index of the array (therefore, the callback is provided with the correct index / value). This sums up with each nested call, and it is recommended that you also be careful when it is not nested, since the next .map() can be called before the first array is collected by the garbage collector (if at all).

Take this example:

 var cb = *some callback function* var arr1 , arr2 , arr3 = [*some large data set] arr1.map(v => { *do something }) cb(arr1) arr2.map(v => { *do something // even though v is overwritten, and the first array // has been passed through, it is still in memory // because of the cached calls to the callback function }) 

If we change this to:

 for(var|let|const v in|of arr1) { *do something } cb(arr1) for(var|let|const v in|of arr2) { *do something // Here there is not callback function to // store a reference for, and the array has // already been passed of (gone out of scope) // so the garbage collector has an opportunity // to remove the array if it runs low on memory } 

I hope this makes some sense (I don't have a better way with words) and helps some to prevent a head scratch through which I went

If anyone is interested, here is another performance test comparing a card and loops (not my job).

https://github.com/dg92/Performance-Analysis-JS

Because loops are usually better than a map, but do not reduce, filter or find

+2
Dec 17 '18 at 22:54
source share

If you do not want to implement your own shell, you can use a queuing system, for example. async.queue , queue .

+1
Dec 05 '14 at 10:52
source share

With regard to increasing the maximum stack size on 32-bit and 64-bit machines, the default values ​​for V8 memory allocation are 700 MB and 1400 MB, respectively. In new versions of V8, memory limits on 64-bit systems are no longer set by V8, theoretically not indicating a limit. However, the OS (operating system) that Node runs on can always limit the amount of memory that V8 can take, so the true limit of any given process cannot be stated at all.

Although V8 provides the option --max_old_space_size , which allows you to control the amount of available memory for the process , taking the value in MB. If you need to increase memory allocation, simply pass this option the desired value when creating the Node process.

This is often a great strategy to reduce the available memory allocation for a given Node instance, especially when running many instances. As with the limitations of the stack, consider whether you need to delegate the huge memory needs to a dedicated storage tier, such as a database in memory or similar.

0
Oct 28 '15 at 16:40
source share

Verify that the function you are importing and the one you specified in the same file do not have the same name.

I will give you an example of this error. In Express JS (using ES6), consider the following scenario:

 import {getAllCall} from '../../services/calls'; let getAllCall = () => { return getAllCall().then(res => { //do something here }) } module.exports = { getAllCall } 

The above script will cause the infamous RangeError: the maximum stack size of the stack , since the function continues to call itself so many times that the maximum call stack ends.

In most cases, an error in the code (for example, above). Another solution is to manually increase the call stack. Well, this works for certain extreme cases, but it is not recommended.

I hope my answer helped you.

0
Dec 14 '17 at 20:08
source share

I thought of a different approach using function references that limit the size of the call stack without using setTimeout() (Node.js, v10.16.0):

testLoop.js

 let counter = 0; const max = 1000000000n // 'n' signifies BigInteger Error.stackTraceLimit = 100; const A = () => { fp = B; } const B = () => { fp = A; } let fp = B; const then = process.hrtime.bigint(); for(;;) { counter++; if (counter > max) { const now = process.hrtime.bigint(); const nanos = now - then; console.log({ "runtime(sec)": Number(nanos) / (1000000000.0) }) throw Error('exit') } fp() continue; } 

exit:

 $ node testLoop.js { 'runtime(sec)': 18.947094799 } C:\Users\jlowe\Documents\Projects\clearStack\testLoop.js:25 throw Error('exit') ^ Error: exit at Object.<anonymous> (C:\Users\jlowe\Documents\Projects\clearStack\testLoop.js:25:11) at Module._compile (internal/modules/cjs/loader.js:776:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:787:10) at Module.load (internal/modules/cjs/loader.js:653:32) at tryModuleLoad (internal/modules/cjs/loader.js:593:12) at Function.Module._load (internal/modules/cjs/loader.js:585:3) at Function.Module.runMain (internal/modules/cjs/loader.js:829:12) at startup (internal/bootstrap/node.js:283:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3) 
0
Jul 10 '19 at 23:39
source share



All Articles