[node.js] 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
...
});
이제 라운드 당 약간의 시간 (브라우저 왕복 1 회)이 느슨해 지므로 루프가 너무 느려질 수 있습니다. 하지만 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 루프는 일반적으로 맵보다 좋지만 축소, 필터링 또는 찾기는 아닙니다.
답변
사전 :
나에게 Max 호출 스택이있는 프로그램은 내 코드 때문이 아니 었습니다. 결국 응용 프로그램 흐름의 정체를 유발하는 다른 문제가되었습니다. 그래서 구성 기회없이 mongoDB에 너무 많은 항목을 추가하려고했기 때문에 호출 스택 문제가 발생했고 무슨 일이 일어나고 있는지 파악하는 데 며칠이 걸렸습니다 ….
@Jeff Lowery가 대답 한 내용에 대한 후속 조치 :이 대답이 너무 즐거웠고 내가하고있는 작업의 프로세스 속도를 최소한 10 배나 빨랐습니다.
나는 프로그래밍에 익숙하지 않지만 대답을 모듈화하려고 시도했습니다. 또한 오류가 발생하는 것을 좋아하지 않았으므로 대신 do while 루프로 래핑했습니다. 내가 한 일이 잘못된 경우 언제든지 수정하십시오.
module.exports = function(object) {
const { max = 1000000000n, fn } = object;
let counter = 0;
let running = true;
Error.stackTraceLimit = 100;
const A = (fn) => {
fn();
flipper = B;
};
const B = (fn) => {
fn();
flipper = A;
};
let flipper = B;
const then = process.hrtime.bigint();
do {
counter++;
if (counter > max) {
const now = process.hrtime.bigint();
const nanos = now - then;
console.log({ 'runtime(sec)': Number(nanos) / 1000000000.0 });
running = false;
}
flipper(fn);
continue;
} while (running);
};
이 요점을 확인하여 내 파일과 루프를 호출하는 방법을 확인하십시오.
https://gist.github.com/gngenius02/3c842e5f46d151f730b012037ecd596c
답변
자체 래퍼를 구현하지 않으려면 async.queue , queue 와 같은 대기열 시스템을 사용할 수 있습니다 .
답변
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;
}
산출:
$ 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)