Kiedy uruchamiam kod, Node.js zgłasza "RangeError: Maximum call stack size exceeded"
wyjątek spowodowany zbyt dużą liczbą wywołań rekurencyjnych. Próbowałem zwiększyć rozmiar stosu Node.js o sudo node --stack-size=16000 app
, ale Node.js ulega awarii bez żadnego komunikatu o błędzie. Gdy uruchomię to ponownie bez sudo, a następnie node.js wydruki 'Segmentation fault: 11'
. Czy istnieje możliwość rozwiązania tego problemu bez usuwania wywołań rekurencyjnych?
node.js
recursion
stack-overflow
callstack
user1518183
źródło
źródło
Segmentation fault: 11
zwykle oznacza błąd w węźle.Odpowiedzi:
Powinieneś zawinąć wywołanie funkcji rekurencyjnej w plik
setTimeout
,setImmediate
lubprocess.nextTick
funkcja, aby dać node.js szansę na wyczyszczenie stosu. Jeśli tego nie zrobisz i istnieje wiele pętli bez prawdziwego wywołania funkcji asynchronicznej lub jeśli nie będziesz czekać na wywołanie zwrotne,
RangeError: Maximum call stack size exceeded
będzie to nieuniknione .Istnieje wiele artykułów dotyczących „Potencjalnej pętli asynchronicznej”. Oto jeden .
Teraz trochę więcej przykładowego kodu:
// 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 ... });
To prawda:
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 ... });
Teraz Twoja pętla może stać się zbyt wolna, ponieważ tracimy trochę czasu (jedna podróż w obie strony przeglądarki) na rundę. Ale nie musisz sprawdzać
setTimeout
w każdej rundzie. Zwykle można to robić co tysięczny raz. Ale może się to różnić w zależności od rozmiaru twojego stosu: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 ... });
źródło
Znalazłem brudne rozwiązanie:
/bin/bash -c "ulimit -s 65500; exec /usr/local/bin/node --stack-size=65500 /path/to/app.js"
Po prostu zwiększa limit stosu wywołań. Myślę, że to nie jest odpowiednie dla kodu produkcyjnego, ale potrzebowałem go do skryptu, który był uruchamiany tylko raz.
źródło
W niektórych językach można to rozwiązać za pomocą optymalizacji wywołań ogonowych, w której wywołanie rekurencji jest przekształcane pod maską w pętlę, więc nie występuje błąd osiągnięcia maksymalnego rozmiaru stosu.
Ale w javascript obecne silniki tego nie obsługują, jest to przewidziane dla nowej wersji języka Ecmascript 6 .
Node.js ma kilka flag włączających funkcje ES6, ale wywołanie ogonowe nie jest jeszcze dostępne.
Możesz więc refaktoryzować swój kod, aby zaimplementować technikę zwaną trampolinowaniem lub refaktoryzacją w celu przekształcenia rekursji w pętlę .
źródło
Miałem podobny problem jak ten. Miałem problem z używaniem wielu Array.map () w rzędzie (około 8 map na raz) i otrzymywałem błąd maximum_call_stack_exceeded. Rozwiązałem ten problem, zmieniając mapę na pętle „for”
Więc jeśli używasz wielu wywołań map, zmiana ich na pętle for może rozwiązać problem
Edytować
Dla jasności i prawdopodobnie nie-potrzebnych-ale-dobrze-wiedzieć-informacji, użycie
.map()
powoduje przygotowanie tablicy (rozwiązywanie funkcji pobierających itp.), A wywołanie zwrotne jest buforowane, a także wewnętrznie zachowuje indeks tablicy ( więc wywołanie zwrotne zawiera poprawny indeks / wartość). Jest to układane w stosy z każdym wywołaniem zagnieżdżonym i zaleca się ostrożność, gdy nie jest również zagnieżdżone, ponieważ następna.map()
może zostać wywołana, zanim pierwsza tablica zostanie wyrzucona do pamięci (jeśli w ogóle).Weź ten przykład:
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 })
Jeśli zmienimy to na:
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 }
Mam nadzieję, że to ma jakiś sens (nie mam najlepszego sposobu na słowa) i pomaga kilku, aby zapobiec drapaniu głowy, przez które przeszedłem
Jeśli ktoś jest zainteresowany, tutaj jest również test wydajnościowy porównujący mapę i pętle (nie moja praca).
https://github.com/dg92/Performance-Analysis-JS
Pętle For są zwykle lepsze niż mapowanie, ale nie redukują, nie filtrują ani nie znajdują
źródło
Przed:
dla mnie program ze stosem wywołań Max nie był spowodowany moim kodem. Skończyło się na tym, że był to inny problem, który spowodował zatory w przepływie aplikacji. Więc ponieważ próbowałem dodać zbyt wiele elementów do mongoDB bez żadnych szans na konfigurację, pojawił się problem ze stosem wywołań i zajęło mi kilka dni, aby dowiedzieć się, co się dzieje ...
Kontynuując to, co odpowiedział @Jeff Lowery: Tak bardzo podobała mi się ta odpowiedź i przyspieszyła proces tego, co robiłem, co najmniej 10x.
Jestem nowy w programowaniu, ale próbowałem modularyzować odpowiedź na to pytanie. Poza tym nie podobał mi się wyrzucany błąd, więc zamiast tego owinąłem go w pętlę do while. Jeśli cokolwiek zrobiłem, jest nieprawidłowe, prosimy o poprawienie mnie.
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); };
Zapoznaj się z tym streszczeniem, aby zobaczyć moje pliki i jak wywołać pętlę. https://gist.github.com/gngenius02/3c842e5f46d151f730b012037ecd596c
źródło
Jeśli nie chcesz implementować własnego opakowania, możesz skorzystać z systemu kolejek, np. Async.queue , queue .
źródło
Pomyślałem o innym podejściu wykorzystującym odwołania do funkcji, które ograniczają rozmiar stosu wywołań bez użycia
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; }
wynik:
$ 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)
źródło
Jeśli chodzi o zwiększenie maksymalnego rozmiaru stosu, na komputerach 32-bitowych i 64-bitowych domyślne ustawienia alokacji pamięci V8 to odpowiednio 700 MB i 1400 MB. W nowszych wersjach V8 limity pamięci w systemach 64-bitowych nie są już ustawiane przez V8, co teoretycznie oznacza brak ograniczeń. Jednak system operacyjny (system operacyjny), na którym działa Node, może zawsze ograniczyć ilość pamięci, którą V8 może zająć, więc nie można ogólnie określić prawdziwego limitu dowolnego procesu.
Chociaż V8 udostępnia
--max_old_space_size
opcję, która pozwala kontrolować ilość pamięci dostępnej dla procesu , akceptując wartość w MB. Jeśli potrzebujesz zwiększyć przydział pamięci, po prostu przekaż tej opcji żądaną wartość podczas tworzenia procesu Node.Często doskonałą strategią jest zmniejszenie dostępnej alokacji pamięci dla danej instancji Node, zwłaszcza w przypadku uruchamiania wielu instancji. Podobnie jak w przypadku limitów stosu, rozważ, czy ogromne zapotrzebowanie na pamięć jest lepiej delegowane do dedykowanej warstwy pamięci, takiej jak baza danych w pamięci lub podobna.
źródło
Sprawdź, czy funkcja, którą importujesz i ta, którą zadeklarowałeś w tym samym pliku, nie mają takiej samej nazwy.
Podam przykład tego błędu. W ekspresowym JS (używając ES6) rozważ następujący scenariusz:
import {getAllCall} from '../../services/calls'; let getAllCall = () => { return getAllCall().then(res => { //do something here }) } module.exports = { getAllCall }
Powyższy scenariusz spowoduje niesławny RangeError: Przekroczono maksymalny rozmiar stosu wywołań ponieważ funkcja wywołuje się tak wiele razy, że zabraknie jej maksymalnego stosu wywołań.
W większości przypadków błąd jest w kodzie (jak ten powyżej). Innym sposobem rozwiązania problemu jest ręczne zwiększenie stosu wywołań. Cóż, działa to w niektórych ekstremalnych przypadkach, ale nie jest zalecane.
Mam nadzieję, że moja odpowiedź ci pomogła.
źródło
Możesz użyć pętli dla.
var items = {1, 2, 3} for(var i = 0; i < items.length; i++) { if(i == items.length - 1) { res.ok(i); } }
źródło
var items = {1, 2, 3}
nie jest poprawną składnią JS. jak to w ogóle ma związek z pytaniem?