Development Tip

Node.js-최대 호출 스택 크기 초과

yourdevel 2020. 11. 14. 11:13
반응형

Node.js-최대 호출 스택 크기 초과


내 코드를 실행할 때 Node.js는 "RangeError: Maximum call stack size exceeded"너무 많은 재귀 호출로 인해 예외를 발생시킵니다. Node.js 스택 크기를으로 늘리려 고 sudo node --stack-size=16000 app했지만 Node.js가 오류 메시지없이 충돌합니다. sudo없이 이것을 다시 실행하면 Node.js는 'Segmentation fault: 11'. 재귀 호출을 제거하지 않고이 문제를 해결할 수 있습니까?


재귀 함수 호출을

  • setTimeout,
  • setImmediate 또는
  • process.nextTick

node.js에게 스택을 지울 수있는 기회를 제공하는 함수입니다. 당신은 그렇게하지 않고 거기에 많은 루프는없는 경우 실제 비동기 함수 호출이나 콜백을 기다리는하지 않는 경우, 당신이 RangeError: Maximum call stack size exceeded될 것입니다 피할 수 .

"잠재적 비동기 루프"에 관한 많은 기사가 있습니다. 여기 하나가 있습니다.

이제 더 많은 예제 코드 :

// 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매 라운드마다 콜할 필요는 없습니다 . 일반적으로 1,000 회마다 수행하는 것이 좋습니다. 그러나 이것은 스택 크기에 따라 다를 수 있습니다.

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"

호출 스택 제한을 늘립니다. 프로덕션 코드에는 적합하지 않다고 생각하지만 한 번만 실행되는 스크립트에는 필요했습니다.


일부 언어에서는 재귀 호출이 내부적으로 루프로 변환되어 최대 스택 크기에 도달 한 오류가없는 테일 호출 최적화로 해결할 수 있습니다.

그러나 자바 스크립트에서는 현재 엔진이이를 지원하지 않으며 Ecmascript 6 언어의 새 버전이 예상됩니다 .

Node.js에는 ES6 기능을 활성화하는 몇 가지 플래그가 있지만 테일 호출은 아직 사용할 수 없습니다.

따라서 코드를 리팩터링하여 trampolining 이라는 기술을 구현 하거나 재귀를 루프로 변환 하기 위해 리팩토링 할 수 있습니다 .


나는 이것과 비슷한 문제가 있었다. 한 행에 여러 개의 Array.map ()을 사용하는 데 문제가 있었고 (한 번에 약 8 개의 맵) maximum_call_stack_exceeded 오류가 발생했습니다. 맵을 'for'루프로 변경하여이 문제를 해결했습니다.

따라서 맵 호출을 많이 사용하는 경우 for 루프로 변경하면 문제가 해결 될 수 있습니다.

편집하다

명확성을 위해 필요하지 않지만 알아두면 좋은 정보를 제공하기 위해 using을 사용 .map()하면 배열이 준비되고 (getters 확인 등) 콜백이 캐시되고 내부적으로 배열의 인덱스가 유지됩니다 ( 따라서 콜백에 올바른 인덱스 / 값이 제공됩니다. 이것은 각 중첩 된 호출과 함께 스택되며 중첩되지 않은 경우에도주의 .map()해야합니다. 첫 번째 배열이 가비지 수집되기 전에 다음 이 호출 될 수 있기 때문입니다 (아마도).

이 예를 보자 :

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
}) 

이것을 다음과 같이 변경하면 :

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
}

나는 이것이 의미가 있기를 바랍니다 (나는 말로 최선의 방법이 없습니다) 그리고 내가 겪은 머리 긁힘을 방지하는 데 도움이되기를 바랍니다.

관심이 있으시면 map과 for 루프를 비교하는 성능 테스트도 있습니다 (내 작업이 아님).

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

For 루프는 일반적으로 맵보다 낫지 만 축소, 필터링 또는 찾기는 아닙니다.


If you don't want to implement your own wrapper, you can use a queue system, e.g. async.queue, queue.


Regarding increasing the max stack size, on 32 bit and 64 bit machines V8's memory allocation defaults are, respectively, 700 MB and 1400 MB. In newer versions of V8, memory limits on 64 bit systems are no longer set by V8, theoretically indicating no limit. However, the OS (Operating System) on which Node is running can always limit the amount of memory V8 can take, so the true limit of any given process cannot be generally stated.

Though V8 makes available the --max_old_space_size option, which allows control over the amount of memory available to a process, accepting a value in MB. Should you need to increase memory allocation, simply pass this option the desired value when spawning a Node process.

It is often an excellent strategy to reduce the available memory allocation for a given Node instance, especially when running many instances. As with stack limits, consider whether massive memory needs are better delegated to a dedicated storage layer, such as an in-memory database or similar.


Please check that the function you are importing and the one that you have declared in the same file do not have the same name.

I will give you an example for 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 scenario will cause infamous RangeError: Maximum call stack size exceeded error because the function keeps calling itself so many times that it runs out of maximum call stack.

Most of the times the error is in code (like the one above). Other way of resolving is manually increasing the call stack. Well, this works for certain extreme cases, but it is not recommended.

Hope my answer helped you.


I thought of another approach using function references that limits call stack size 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;
}

output:

$ 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)

참고URL : https://stackoverflow.com/questions/20936486/node-js-maximum-call-stack-size-exceeded

반응형