Node.js - अधिकतम कॉल स्टैक आकार पार हो गया


80

जब मैं अपना कोड चलाता हूं, तो Node.js "RangeError: Maximum call stack size exceeded"बहुत अधिक पुनरावर्ती कॉलों के कारण अपवाद छोड़ देता है। मैंने Node.js के स्टैक के आकार को बढ़ाने की कोशिश की sudo node --stack-size=16000 app, लेकिन Node.js बिना किसी त्रुटि संदेश के क्रैश हो गया। जब मैं इसे बिना सूडो के फिर से चलाता हूं, तो Node.js प्रिंट करता है 'Segmentation fault: 11'। क्या मेरे पुनरावर्ती कॉल को हटाने के बिना इसे हल करने की संभावना है?


3
आपको पहली बार ऐसी गहरी पुनरावृत्ति की आवश्यकता क्यों है?
दान अब्रामोव

1
कृपया, क्या आप कुछ कोड पोस्ट कर सकते हैं? Segmentation fault: 11आमतौर पर नोड में एक बग का मतलब है।
vkurchatkin

1
@ दान अब्रामोव: गहरी पुनरावृत्ति क्यों? यह एक समस्या हो सकती है यदि आप किसी सरणी या सूची पर पुनरावृति करना चाहते हैं और प्रत्येक पर एक async ऑपरेशन करते हैं (जैसे कुछ डेटाबेस ऑपरेशन)। यदि आप अगले आइटम पर जाने के लिए async ऑपरेशन से कॉलबैक का उपयोग करते हैं, तो सूची में प्रत्येक आइटम के लिए कम से कम एक अतिरिक्त स्तर होगा। नीचे हाइनोब द्वारा प्रदान किया गया विरोधी पैटर्न स्टैक को बाहर बहने से रोकता है।
फिलिप कालेंडर

1
@PhilipCallender मुझे एहसास नहीं हुआ कि आप async सामान कर रहे थे, स्पष्टीकरण के लिए धन्यवाद!
दान अब्रामोव

@DanAbramov को क्रैश करने के लिए या तो गहरा होना जरूरी नहीं है। V8 को स्टैक पर आवंटित सामान को साफ करने का मौका नहीं मिलता है। पहले कहे जाने वाले कार्य जो लंबे समय से निष्पादित करना बंद कर रहे हैं, हो सकता है कि स्टैक पर चर बनाए गए हों, जिन्हें अब संदर्भित नहीं किया गया है लेकिन फिर भी स्मृति में रखा गया है। यदि आप किसी समकालिक फैशन में कोई गहन समय लेने वाला ऑपरेशन कर रहे हैं और स्टैक पर वैरिएबल आवंटित कर रहे हैं, तो आप अभी भी उसी त्रुटि के साथ क्रैश कर रहे हैं। मैं अपने तुल्यकालिक JSON पार्सर को 9 की कॉलस्टैक गहराई पर दुर्घटनाग्रस्त हो गया। kikobeats.com/synchronously-asynchronous
FeignMan

जवाबों:


114

आपको अपने पुनरावर्ती फ़ंक्शन कॉल को एक में लपेटना चाहिए

  • setTimeout,
  • setImmediate या
  • process.nextTick

नोड को देने के लिए फ़ंक्शन। स्टैक को खाली करने का मौका देता है। यदि आप ऐसा नहीं करते हैं और बिना किसी वास्तविक async फ़ंक्शन कॉल के कई लूप हैं या यदि आप कॉलबैक की प्रतीक्षा नहीं करते हैं, तो आपका अपरिहार्यRangeError: Maximum call stack size exceeded होगा ।

"संभावित Async लूप" के विषय में कई लेख हैं। यहाँ एक है

अब कुछ और उदाहरण कोड:

// 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
    ...
});

6
आपके उत्तर में कुछ अच्छे और बुरे अंक थे। मुझे वास्तव में पसंद आया कि आपने सेटटाइमआउट () एट अल का उल्लेख किया। लेकिन setTimeout (fn, 1) का उपयोग करने की कोई आवश्यकता नहीं है, क्योंकि setTimeout (fn, 0) पूरी तरह से ठीक है (इसलिए हमें setTimeout (fn, 1) हर% 1000 हैक की आवश्यकता नहीं है)। यह जावास्क्रिप्ट वीएम को स्टैक को खाली करने और तुरंत निष्पादन को फिर से शुरू करने की अनुमति देता है। Node.js में process.nextTick () थोड़ा बेहतर है क्योंकि यह आपके कॉलबैक को फिर से शुरू करने से पहले कुछ अन्य सामान (I / O IIRC) करने के लिए नोड की अनुमति देता है।
joonas.fi

2
मैं कहूंगा कि इन मामलों में setTimeout के बजाय setImmediate का उपयोग करना बेहतर है।
BaNz

4
@ joonas.fi: मेरा 1000% के साथ "हैक" आवश्यक है। हर लूप पर एक सेटमीडिएट / सेटटाइमआउट (0 के साथ भी) नाटकीय रूप से धीमा है।
हेनोब

3
अंग्रेजी अनुवाद के साथ अपने इन-कोड जर्मन टिप्पणियों को अपडेट करने के लिए ...? :) मुझे समझ में आता है लेकिन अन्य लोग इतने भाग्यशाली नहीं हो सकते हैं।
रॉबर्ट रॉसमैन


30

मुझे एक गंदा समाधान मिला:

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

यह सिर्फ कॉल स्टैक सीमा को बढ़ाता है। मुझे लगता है कि यह उत्पादन कोड के लिए उपयुक्त नहीं है, लेकिन मुझे इसकी स्क्रिप्ट की जरूरत थी जो केवल एक बार चलती है।


कूल चाल, हालांकि व्यक्तिगत रूप से मैं गलतियों से बचने और अधिक अच्छी तरह गोल समाधान बनाने के लिए सही प्रथाओं का उपयोग करने का सुझाव दूंगा।
डिकोडर 7283

मेरे लिए यह एक अनब्लॉकिंग समाधान था। मेरे पास एक परिदृश्य था जहां मैं एक डेटाबेस के तीसरे पक्ष के उन्नयन की स्क्रिप्ट चला रहा था और सीमा त्रुटि प्राप्त कर रहा था। मैं तीसरे पक्ष के पैकेज को फिर से लिखने के लिए नहीं जा रहा था, लेकिन डेटाबेस को अपग्रेड करने की आवश्यकता थी → इसने इसे ठीक कर दिया।
टिम कॉक

7

कुछ भाषाओं में इसे टेल कॉल ऑप्टिमाइज़ेशन के साथ हल किया जा सकता है, जहाँ रिकर्सन कॉल को हुड के नीचे एक लूप में बदल दिया जाता है, ताकि कोई अधिकतम स्टैक साइज त्रुटि तक न पहुंचे।

लेकिन जावास्क्रिप्ट में वर्तमान इंजन इसका समर्थन नहीं करते हैं, यह भाषा के नए संस्करण Ecmascript 6 के लिए महत्वपूर्ण है

ES6 सुविधाओं को सक्षम करने के लिए Node.js के पास कुछ झंडे हैं, लेकिन टेल कॉल अभी तक उपलब्ध नहीं है।

तो आप अपने कोड को ट्रम्पोलिनिंग नामक तकनीक को लागू करने के लिए रिफ्लेक्टर कर सकते हैं , या पुनरावृत्ति को एक लूप में बदल सकते हैं


धन्यवाद। मेरा पुनरावर्तन कॉल मान वापस नहीं करता है, तो क्या फ़ंक्शन को कॉल करने और परिणाम की प्रतीक्षा करने का कोई तरीका नहीं है?
user1518183

और क्या यह फ़ंक्शन कुछ डेटा को बदल देता है, एक सरणी की तरह, यह फ़ंक्शन क्या करता है, इनपुट / आउटपुट क्या हैं?
कोणीय विश्वविद्यालय

5

मेरे पास इस तरह का एक मुद्दा था। मेरे पास एक पंक्ति में कई Array.map () का उपयोग करने के साथ एक समस्या थी (लगभग 8 नक्शे एक बार में) और अधिकतम_call_stack_exceeded त्रुटि हो रही थी। मैंने इसे 'छोरों' के लिए मानचित्र के रूप में बदलकर हल किया

इसलिए यदि आप मानचित्र कॉल का एक बहुत उपयोग कर रहे हैं, तो उन्हें लूप में बदलने से समस्या ठीक हो सकती है

संपादित करें

केवल स्पष्टता और शायद-नहीं-लेकिन-अच्छी-से-जानने के लिए जानकारी के .map()कारण, सरणी का उपयोग करने का कारण बनता है (गेट्स, आदि को हल करना) और कैशबैक को कैश किया जा सकता है, और आंतरिक रूप से सरणी का एक सूचकांक भी रखता है। इसलिए कॉलबैक को सही सूचकांक / मूल्य के साथ प्रदान किया जाता है)। प्रत्येक नेस्टेड कॉल के साथ यह स्टैक, और सावधानी बरतने की सलाह दी जाती है जब साथ ही नेस्टेड न हो, क्योंकि अगले .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
}

मुझे आशा है कि यह कुछ समझ में आता है (मेरे पास शब्दों के साथ सबसे अच्छा तरीका नहीं है) और कुछ सिर को खरोंचने से रोकने में मदद करता है जिससे मैं गुजरा था

अगर किसी को दिलचस्पी है, तो यहां एक प्रदर्शन परीक्षण है जो मानचित्र की तुलना करता है और लूप के लिए (मेरे काम नहीं)।

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

छोरों के लिए आमतौर पर नक्शे से बेहतर होते हैं, लेकिन कम नहीं करते हैं, फ़िल्टर करते हैं, या पाते हैं


कुछ महीने पहले जब मैंने आपकी प्रतिक्रिया पढ़ी तो मुझे पता नहीं था कि आपके उत्तर में जो सोना था। मैंने हाल ही में अपने लिए एक ही चीज़ की खोज की है और इसके कारण मुझे वास्तव में मेरे पास जो कुछ भी है, उसे अनजान बनाना चाहता है, कभी-कभी पुनरावृत्तियों के रूप में सोचना मुश्किल है। उम्मीद है कि यह मदद करता है :: मैंने एक अतिरिक्त उदाहरण लिखा है जिसमें लूप के हिस्से के रूप में वादे शामिल हैं और दिखाता है कि आगे बढ़ने से पहले प्रतिक्रिया का इंतजार कैसे करें। उदाहरण: gist.github.com/gngenius02/…
7:22 पर

मुझे लगता है कि तुमने वहाँ क्या किया (और आशा है कि तुम बुरा नहीं मानोगे अगर मैं अपने टूलबॉक्स को छीन लूँ)। मैं ज्यादातर सिंक्रोनस कोड का उपयोग करता हूं, यही वजह है कि मैं आमतौर पर लूप पसंद करता हूं। लेकिन यह एक रत्न है जो आपको वहां भी मिला है, और सबसे अधिक संभावना है कि मैं अगले सर्वर पर अपना रास्ता
ढूंढ लूंगा

2

पूर्व:

मेरे लिए मैक्स कॉल स्टैक वाला प्रोग्राम मेरे कोड के कारण नहीं था। यह एक अलग मुद्दा है जो आवेदन के प्रवाह में भीड़ का कारण बना समाप्त हो गया। इसलिए क्योंकि मैं किसी भी कॉन्फ़िगरेशन संभावना के बिना mongoDB में बहुत अधिक आइटम जोड़ने की कोशिश कर रहा था, कॉल स्टैक समस्या पॉपिंग थी और मुझे यह पता लगाने में कुछ दिन लग गए कि क्या चल रहा था .... उसने कहा:


@Jeff Lowery ने जो उत्तर दिया उसके साथ: मैंने इस उत्तर का बहुत आनंद लिया और इसने इस प्रक्रिया को प्रभावित किया कि मैं कम से कम 10x तक क्या कर रहा था।

मैं प्रोग्रामिंग में नया हूँ, लेकिन मैंने इसका उत्तर संशोधित करने का प्रयास किया। इसके अलावा, त्रुटि को फेंका जाना पसंद नहीं था इसलिए मैंने इसे लूप के बजाय डू में लपेट दिया। अगर मैंने कुछ भी गलत किया है, तो कृपया मुझे सुधारने के लिए स्वतंत्र महसूस करें।

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


1

यदि आप अपने स्वयं के आवरण को लागू नहीं करना चाहते हैं, तो आप एक कतार प्रणाली का उपयोग कर सकते हैं, जैसे कि async.queue , कतार


1

मैंने फ़ंक्शन संदर्भों का उपयोग करते हुए एक और दृष्टिकोण के बारे में सोचा जो बिना उपयोग के स्टैक आकार को सीमित करता है 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)

0

अधिकतम स्टैक आकार में वृद्धि के बारे में, 32 बिट और 64 बिट मशीनों पर क्रमशः V8 की मेमोरी आवंटन चूक, 700 एमबी और 14 बिट एमबी हैं। V8 के नए संस्करणों में, 64 बिट सिस्टम पर मेमोरी सीमाएं V8 द्वारा सेट नहीं की जाती हैं, सैद्धांतिक रूप से कोई सीमा नहीं दर्शाती है। हालाँकि, OS (ऑपरेटिंग सिस्टम) जिस पर नोड चल रहा है वह हमेशा मेमोरी V8 की मात्रा को सीमित कर सकता है, इसलिए किसी भी प्रक्रिया की सही सीमा आमतौर पर नहीं बताई जा सकती है।

यद्यपि V8 --max_old_space_sizeविकल्प उपलब्ध कराता है , जो एमबी में एक मूल्य को स्वीकार करते हुए, एक प्रक्रिया को उपलब्ध स्मृति की मात्रा पर नियंत्रण की अनुमति देता है । क्या आपको मेमोरी आवंटन बढ़ाने की आवश्यकता है, बस नोड विकल्प की प्रक्रिया के दौरान इस विकल्प को वांछित मान पास करें।

यह अक्सर दिए गए नोड उदाहरण के लिए उपलब्ध मेमोरी आवंटन को कम करने के लिए एक उत्कृष्ट रणनीति है, खासकर जब कई उदाहरण चल रहे हों। स्टैक सीमा के साथ, इस बात पर विचार करें कि क्या बड़े पैमाने पर मेमोरी की जरूरतों को एक समर्पित स्टोरेज लेयर में बेहतर तरीके से प्रत्यायोजित किया जाता है, जैसे इन-मेमोरी डेटाबेस या समान।


0

कृपया जांचें कि आप जो फ़ंक्शन आयात कर रहे हैं और जिस फ़ाइल को आपने एक ही फ़ाइल में घोषित किया है, उसका नाम समान नहीं है।

मैं आपको इस त्रुटि के लिए एक उदाहरण दूंगा। एक्सप्रेस JS (ES6 का उपयोग करके), इस परिदृश्य पर विचार करें:

import {getAllCall} from '../../services/calls';

let getAllCall = () => {
   return getAllCall().then(res => {
      //do something here
   })
}
module.exports = {
getAllCall
}

उपरोक्त परिदृश्य में बदनाम रेंजयर्रर का कारण होगा : अधिकतम कॉल स्टैक आकार त्रुटि से अधिक हो गया क्योंकि फ़ंक्शन खुद को कई बार कॉल करता रहता है ताकि यह अधिकतम कॉल स्टैक से बाहर हो जाए।

अधिकांश बार त्रुटि कोड में होती है (जैसे ऊपर वाला)। समाधान का अन्य तरीका मैन्युअल रूप से कॉल स्टैक को बढ़ा रहा है। खैर, यह कुछ चरम मामलों के लिए काम करता है, लेकिन यह अनुशंसित नहीं है।

आशा है कि मेरे उत्तर ने आपकी मदद की।


-4

आप के लिए लूप का उपयोग कर सकते हैं।

var items = {1, 2, 3}
for(var i = 0; i < items.length; i++) {
  if(i == items.length - 1) {
    res.ok(i);
  }
}

2
var items = {1, 2, 3}कोई मान्य JS सिंटैक्स नहीं है। यह सवाल कैसे संबंधित है?
मूसिंड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.