Node.js – 超过最大调用堆栈大小
当我运行我的代码,Node.js抛出"RangeError: Maximum call stack size exceeded"
exception引起的recursion调用太多。 我试图通过sudo node --stack-size=16000 app
来增加Node.js的堆栈大小,但Node.js崩溃而没有任何错误信息。 当我没有sudo再次运行这个,然后Node.js打印'Segmentation fault: 11'
。 有没有可能解决这个问题,而不删除recursion调用?
谢谢
你应该把你的recursion函数调用包装成一个
-
setTimeout
, -
setImmediate
或 -
process.nextTick
函数给node.js清除堆栈的机会。 如果你不这样做,并且没有任何真正的asynchronous函数调用有很多循环,或者如果你不等待callback,你的RangeError: Maximum call stack size exceeded
将是不可避免的 。
有很多关于“潜在的asynchronous循环”的文章。 这是一个 。
现在再来一些示例代码:
// 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 ... });
这是对的:
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 ... });
现在你的循环可能会变得太慢,因为我们每轮都要花一点时间(一次浏览器往返)。 但是你不必在每一轮都调用setTimeout
。 通常每1000次就可以做一次。 但这可能会有所不同,具体取决于您的堆栈大小
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 ... });
我发现一个肮脏的解决
/bin/bash -c "ulimit -s 65500; exec /usr/local/bin/node --stack-size=65500 /path/to/app.js"
它只是增加了调用堆栈限制。 我认为这不适合生产代码,但是我只需要运行一次的脚本。
在某些语言中,可以使用尾部调用优化来解决这个问题,在这种情况下,recursion调用在引擎盖下变成了一个循环,所以没有最大的堆栈大小达到错误。
但在JavaScript中,目前的引擎不支持这一点,预计新版本的语言Ecmascript 6 。
Node.js有一些标志来启用ES6function,但是尾部呼叫还不可用。
所以你可以重构你的代码来实现一个叫做trampolining的技术,或者重构为了把recursion转换成一个循环 。
如果你不想实现自己的包装,你可以使用队列系统,例如async.queue , 队列 。
关于增加最大堆栈大小,在32位和64位机器上,V8的内存分配默认分别是700 MB和1400 MB。 在较新版本的V8中,64位系统的内存限制不再由V8设置,理论上没有限制。 但是,运行Node的OS(操作系统)总是可以限制V8可以使用的内存量,所以任何给定进程的真正限制都不能一概而论。
虽然V8提供了--max_old_space_size
选项,它允许控制进程可用的内存量,接受以MB为单位的值。 如果您需要增加内存分配,只需在产生节点进程时将此选项传递给期望的值。
减less给定节点实例的可用内存分配通常是一个很好的策略,特别是在运行多个实例时。 与堆栈限制一样,考虑将大容量内存需求委派给专用存储层(如内存数据库等)是否更好。