2014-01-05 10 views
60

Wenn ich meinen Code ausführen, werfen Node.js "RangeError: Maximum call stack size exceeded" Ausnahme verursacht durch zu viele Rekursionsaufrufe. Ich habe versucht, Node.js stack-size um sudo node --stack-size=16000 app zu erhöhen, aber Node.js stürzt ohne Fehlermeldung ab. Wenn ich dies ohne sudo erneut ausführe, dann drucke Node.js 'Segmentation fault: 11'. Gibt es eine Möglichkeit, dies zu lösen, ohne Rekursion zu entfernen?Node.js - Maximale Call-Stack-Größe überschritten

Dank

+2

Warum brauchen Sie solche tiefe Rekursion überhaupt? –

+0

Bitte, können Sie etwas Code posten? 'Segmentation fault: 11' bedeutet normalerweise ein Fehler im Knoten. – vkurchatkin

+1

@Dan Abramov: Warum tiefe Rekursion? Dies kann ein Problem sein, wenn Sie über ein Array oder eine Liste iterieren und eine asynchrone Operation für jeden (z. B. eine Datenbankoperation) ausführen möchten. Wenn Sie den Rückruf der asynchronen Operation verwenden, um zum nächsten Element zu gelangen, wird für jedes Element in der Liste mindestens eine zusätzliche Rekursionsebene vorhanden sein. Das unten von heinob bereitgestellte Anti-Pattern verhindert, dass der Stack ausbläst. –

Antwort

77

Sie sollten Ihre rekursiven Funktionsaufruf in eine

  • setTimeout, wickeln
  • setImmediate oder
  • process.nextTick

Funktion node.js die Möglichkeit zu geben, den Stapel zu löschen. Wenn Sie dies nicht tun und es gibt viele Schleifen ohne real async Funktionsaufruf oder wenn Sie nicht auf den Rückruf warten, wird Ihre RangeError: Maximum call stack size exceededunvermeidlich sein.

Es gibt viele Artikel über "Potential Async Loop". Here is one.

nun einige weitere Beispiel-Code:

Dieses Recht ist:

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 ist Ihre Schleife zu langsam werden könnte, weil wir ein wenig Zeit (ein Browser hin und zurück) pro Runde verlieren. Aber Sie müssen setTimeout in jeder Runde nicht anrufen. Normalerweise ist es o.k. um es alle 1000 Mal zu tun. Aber diese unterscheiden sich möglicherweise auf Ihrem Stack-Größe abhängig:

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

Es gab einige gute und schlechte Punkte in Ihrer Antwort. Es hat mir sehr gefallen, dass du setTimeout() et al.Aber es ist nicht notwendig setTimeout (fn, 1) zu verwenden, da setTimeout (fn, 0) vollkommen in Ordnung ist (also brauchen wir nicht setTimeout (fn, 1) für jeden% 1000 Hack). Dadurch kann die JavaScript-VM den Stapel löschen und die Ausführung sofort fortsetzen. In node.js ist die process.nextTick() etwas besser, weil sie es node.js erlaubt, noch etwas anderes zu tun (I/O IIRC), bevor Sie Ihren Callback fortsetzen. –

+1

Sie haben Recht. 0 ist besser. behoben. – heinob

+1

Ich würde sagen, dass es besser ist, SetImmediate anstelle von SetTimeout in diesen Fällen zu verwenden. – BaNz

5

In einigen Sprachen kann dies mit Endrekursion Optimierung gelöst werden, wobei der Rekursion Anruf unter der Haube in eine Schleife umgewandelt wird, so dass keine maximale Stapelgröße existieren erreicht Fehler.

Aber in Javascript die aktuellen Engines unterstützen dies nicht, es ist für die neue Version der Sprache Ecmascript 6 vorgesehen.

Node.js hat einige Flags, um ES6-Funktionen zu aktivieren, aber Tail Call ist noch nicht verfügbar.

Sie können also Ihren Code umgestalten, um eine Technik namens trampolining zu implementieren, oder refactor um transform recursion into a loop.

+0

Vielen Dank. Mein Rekursionsaufruf gibt keinen Wert zurück. Gibt es also eine Möglichkeit, die Funktion aufzurufen und nicht auf das Ergebnis zu warten? – user1518183

+0

Und ändert die Funktion einige Daten, wie ein Array, was macht die Funktion, was sind die Ein-/Ausgänge? –

19

fand ich eine schmutzige Lösung:

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

Es nur Call-Stack Limit erhöhen. Ich denke, dass dies nicht für Produktionscode geeignet ist, aber ich brauchte es für ein Skript, das nur einmal ausgeführt wird.

+13

Ich wünsche Ihnen viel Glück! – heinob

1

Wenn Sie möchten nicht Ihren eigenen Wrapper implementieren, können Sie ein Warteschlangen-System verwenden, z.B. async.queue, queue.

0

In Bezug auf die Erhöhung der maximalen Stapelgröße sind die Speicherzuordnungsvorgaben von V8 bei 32-Bit- und 64-Bit-Maschinen jeweils 700 MB und 1400 MB. In neueren Versionen von V8 werden Speicherbegrenzungen für 64-Bit-Systeme nicht mehr von V8 festgelegt, was theoretisch keine Begrenzung bedeutet. Das Betriebssystem (Betriebssystem), auf dem der Knoten ausgeführt wird, kann jedoch immer die Speichermenge begrenzen, die V8 verarbeiten kann. Daher kann die tatsächliche Grenze eines bestimmten Prozesses nicht allgemein angegeben werden.

Obwohl V8 die --max_old_space_size Option zur Verfügung stellt, die Kontrolle über die Menge an Speicher ermöglicht, die für einen Prozess verfügbar ist, einen Wert in MB zu akzeptieren. Sollten Sie die Speicherzuweisung erhöhen müssen, übergeben Sie diese Option einfach beim Erstellen eines Node-Prozesses.

Es ist oft eine ausgezeichnete Strategie, um die verfügbare Speicherzuweisung für eine bestimmte Node-Instanz zu reduzieren, insbesondere wenn viele Instanzen ausgeführt werden. Berücksichtigen Sie bei Stack-Limits, ob der Bedarf an massivem Speicher besser an eine dedizierte Speicherschicht wie eine speicherinterne Datenbank oder Ähnliches delegiert werden kann.

0

Bitte überprüfen Sie, dass die Funktion, die Sie importieren, und die, die Sie in derselben Datei deklariert haben, nicht denselben Namen haben.

Ich werde Ihnen ein Beispiel für diesen Fehler geben. In Express.js (mit ES6), das folgende Szenario: Maximale Call-Stack-Größe überschritten Fehler, da die Funktion selbst so oft hält fordern, dass es aus läuft:

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

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

Das obige Szenario berüchtigte Auslöser Range verursachen maximaler Aufrufstapel

Meistens ist der Fehler im Code (wie der oben). Eine andere Möglichkeit der Auflösung besteht darin, den Aufrufstapel manuell zu erhöhen. Nun, das funktioniert für bestimmte Extremfälle, aber es wird nicht empfohlen.

Hope meine Antwort hat Ihnen geholfen.