Wenn ich meinen Code ausführe, löst Node.js eine "RangeError: Maximum call stack size exceeded"
Ausnahme aus, die durch zu viele rekursive Aufrufe verursacht wird. Ich habe versucht, die Stapelgröße von sudo node --stack-size=16000 app
Node.j um zu erhöhen , aber Node.js stürzt ohne Fehlermeldung ab. Wenn ich dies erneut ohne sudo ausführe, wird Node.js gedruckt 'Segmentation fault: 11'
. Gibt es eine Möglichkeit, dies zu lösen, ohne meine rekursiven Aufrufe zu entfernen?
node.js
recursion
stack-overflow
callstack
user1518183
quelle
quelle
Segmentation fault: 11
bedeutet normalerweise einen Fehler im Knoten.Antworten:
Sie sollten Ihren rekursiven Funktionsaufruf in a einschließen
setTimeout
,setImmediate
oderprocess.nextTick
Funktion, um node.js die Möglichkeit zu geben, den Stapel zu löschen. Wenn Sie dies nicht tun und es viele Schleifen ohne echten asynchronen Funktionsaufruf gibt oder wenn Sie nicht auf den Rückruf warten,
RangeError: Maximum call stack size exceeded
ist dies unvermeidlich .Es gibt viele Artikel zum Thema "Potential Async Loop". Hier ist einer .
Nun noch ein Beispielcode:
// 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 ... });
Dies ist richtig:
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 ... });
Jetzt wird Ihre Schleife möglicherweise zu langsam, da wir pro Runde etwas Zeit verlieren (ein Browser-Roundtrip). Sie müssen aber nicht
setTimeout
in jeder Runde anrufen . Normalerweise ist es in Ordnung, dies alle 1000. Mal zu tun. Dies kann jedoch je nach Stapelgröße unterschiedlich sein: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 ... });
quelle
Ich habe eine schmutzige Lösung gefunden:
/bin/bash -c "ulimit -s 65500; exec /usr/local/bin/node --stack-size=65500 /path/to/app.js"
Es erhöht nur das Call-Stack-Limit. Ich denke, dass dies nicht für Produktionscode geeignet ist, aber ich brauchte es für Skripte, die nur einmal ausgeführt werden.
quelle
In einigen Sprachen kann dies mit der Tail-Call-Optimierung gelöst werden, bei der der Rekursionsaufruf unter der Haube in eine Schleife umgewandelt wird, sodass kein Fehler beim Erreichen der maximalen Stapelgröße vorliegt.
In Javascript unterstützen die aktuellen Engines dies jedoch nicht. Dies ist für eine neue Version der Sprache Ecmascript 6 vorgesehen .
Node.js verfügt über einige Flags, um ES6-Funktionen zu aktivieren, aber ein Tail Call ist noch nicht verfügbar.
Sie können also Ihren Code umgestalten, um eine Technik namens Trampolin zu implementieren , oder umgestalten, um die Rekursion in eine Schleife umzuwandeln .
quelle
Ich hatte ein ähnliches Problem wie dieses. Ich hatte ein Problem mit der Verwendung mehrerer Array.map () in einer Reihe (ungefähr 8 Karten gleichzeitig) und bekam den Fehler "maximum_call_stack_exceeded". Ich habe dieses Problem gelöst, indem ich die Karte in 'for'-Schleifen geändert habe
Wenn Sie also viele Kartenaufrufe verwenden, kann das Problem durch Ändern in for-Schleifen behoben werden
Bearbeiten
Nur aus Gründen der Klarheit und wahrscheinlich nicht benötigten, aber gut zu informierenden Informationen
.map()
bewirkt die Verwendung, dass das Array vorbereitet wird (Auflösen von Gettern usw.) und der Rückruf zwischengespeichert wird, und dass auch intern ein Index des Arrays gespeichert wird ( Daher wird der Rückruf mit dem richtigen Index / Wert versehen. Dies wird bei jedem verschachtelten Aufruf gestapelt, und Vorsicht ist geboten, wenn es nicht ebenfalls verschachtelt ist, da das nächste.map()
aufgerufen werden kann, bevor das erste Array (wenn überhaupt) mit Müll gesammelt wird.Nehmen Sie dieses Beispiel:
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 })
Wenn wir dies ändern in:
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 }
Ich hoffe, das macht Sinn (ich kann nicht gut mit Worten umgehen) und hilft einigen, das Kratzen des Kopfes zu verhindern, das ich durchgemacht habe
Wenn jemand interessiert ist, ist hier auch ein Leistungstest zum Vergleichen von Karte und für Schleifen (nicht meine Arbeit).
https://github.com/dg92/Performance-Analysis-JS
For-Schleifen sind normalerweise besser als Map, aber nicht reduzieren, filtern oder finden
quelle
Vor:
Für mich lag das Programm mit dem Max-Aufrufstapel nicht an meinem Code. Es war ein anderes Problem, das zu einer Überlastung des Anwendungsflusses führte. Da ich versucht habe, zu viele Elemente zu mongoDB hinzuzufügen, ohne dass Konfigurationsmöglichkeiten bestehen, trat das Problem mit dem Anrufstapel auf und ich brauchte ein paar Tage, um herauszufinden, was los war.
Nachverfolgung der Antwort von @Jeff Lowery: Ich habe diese Antwort sehr genossen und sie hat den Prozess meiner Arbeit mindestens um das 10-fache beschleunigt.
Ich bin neu in der Programmierung, aber ich habe versucht, die Antwort darauf zu modularisieren. Ich mochte es auch nicht, wenn der Fehler ausgelöst wurde, also habe ich ihn stattdessen in eine do while-Schleife eingeschlossen. Wenn etwas, das ich getan habe, falsch ist, können Sie mich gerne korrigieren.
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); };
Schauen Sie sich diese Übersicht an, um meine Dateien zu sehen und wie Sie die Schleife aufrufen. https://gist.github.com/gngenius02/3c842e5f46d151f730b012037ecd596c
quelle
Wenn Sie keinen eigenen Wrapper implementieren möchten, können Sie ein Warteschlangensystem verwenden, z . B. async.queue , queue .
quelle
Ich dachte an einen anderen Ansatz mit Funktionsreferenzen, der die Größe des Aufrufstapels ohne Verwendung begrenzt
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; }
Ausgabe:
$ 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)
quelle
In Bezug auf die Erhöhung der maximalen Stapelgröße betragen die Standardwerte für die Speicherzuweisung von V8 auf 32-Bit- und 64-Bit-Computern 700 MB bzw. 1400 MB. In neueren Versionen von V8 werden Speicherbeschränkungen auf 64-Bit-Systemen nicht mehr von V8 festgelegt, was theoretisch keine Begrenzung anzeigt. Das Betriebssystem (Betriebssystem), auf dem Node ausgeführt wird, kann jedoch immer die Speicherkapazität begrenzen, die V8 beanspruchen kann, sodass die tatsächliche Grenze eines bestimmten Prozesses nicht allgemein angegeben werden kann.
V8 stellt jedoch die
--max_old_space_size
Option zur Verfügung, mit der die für einen Prozess verfügbare Speichermenge gesteuert werden kann, wobei ein Wert in MB akzeptiert wird. Wenn Sie die Speicherzuordnung erhöhen müssen, übergeben Sie dieser Option einfach den gewünschten Wert, wenn Sie einen Knotenprozess erzeugen.Es ist oft eine hervorragende Strategie, die verfügbare Speicherzuordnung für eine bestimmte Knoteninstanz zu reduzieren, insbesondere wenn viele Instanzen ausgeführt werden. Überlegen Sie wie bei Stapelbeschränkungen, ob der massive Speicherbedarf besser an eine dedizierte Speicherschicht delegiert werden kann, z. B. eine speicherinterne Datenbank oder ähnliches.
quelle
Bitte überprüfen Sie, ob die Funktion, die Sie importieren, und die Funktion, die Sie in derselben Datei deklariert haben, nicht denselben Namen haben.
Ich werde Ihnen ein Beispiel für diesen Fehler geben. Betrachten Sie in Express JS (mit ES6) das folgende Szenario:
import {getAllCall} from '../../services/calls'; let getAllCall = () => { return getAllCall().then(res => { //do something here }) } module.exports = { getAllCall }
Das obige Szenario führt zu einem berüchtigten RangeError: Die maximale Größe des Aufrufstapels hat den Fehler überschritten , da sich die Funktion so oft selbst aufruft, dass der maximale Aufrufstapel nicht mehr ausreicht.
Meistens liegt der Fehler im Code vor (wie oben). Eine andere Möglichkeit zum Auflösen besteht darin, den Aufrufstapel manuell zu erhöhen. Nun, dies funktioniert in bestimmten Extremfällen, wird jedoch nicht empfohlen.
Hoffe meine Antwort hat dir geholfen.
quelle
Sie können Schleife für verwenden.
var items = {1, 2, 3} for(var i = 0; i < items.length; i++) { if(i == items.length - 1) { res.ok(i); } }
quelle
var items = {1, 2, 3}
ist keine gültige JS-Syntax. Wie hängt das überhaupt mit der Frage zusammen?