Nœud.js-taille maximale de la pile d'appels dépassée

quand j'exécute mon code, noeud.js throw "RangeError: Maximum call stack size exceeded" exception causée par trop d'appels de récursion. J'ai essayé d'augmenter le Nœud.js pile de taille par sudo node --stack-size=16000 app , mais le Nœud.js crash sans aucun message d'erreur. Quand je l'exécute à nouveau sans sudo, puis noeud.js print 'Segmentation fault: 11' . Est-il possible de résoudre ce sans retirer la récursivité appel?

Merci

62
demandé sur Yves M. 2014-01-05 21:08:47

6 réponses

vous devez envelopper votre appel de fonction récursive dans un

  • setTimeout ,
  • setImmediate ou
  • process.nextTick

fonction pour donner le noeud.js la chance de vider la pile. Si vous ne faites pas cela et qu'il y a beaucoup de boucles sans appel de fonction async réel" 1519200920 ou si vous n'attendez pas le rappel, votre RangeError: Maximum call stack size exceeded sera inévitable .

il y a de nombreux articles concernant"potential Async Loop". En voici un .

maintenant un autre exemple de code:

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

C'est juste:

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

maintenant votre boucle peut devenir trop lente, parce que nous perdons un peu de temps (un tour de navigateur) par tour. Mais vous n'avez pas à appeler setTimeout à chaque tour. Normalement, il est o.k. de le faire tous les temps 1000e. Mais cela peut différer selon la taille de votre pile:

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
    ...
});
85
répondu heinob 2015-12-08 09:00:02

j'ai trouvé une solution sale:

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

il suffit d'augmenter la limite de pile d'appels. Je pense que ce n'est pas approprié pour le code de production, mais j'en avais besoin pour un script qui ne tourne qu'une fois.

19
répondu user1518183 2014-03-25 16:52:59

dans certaines langues, cela peut être résolu avec l'optimisation de l'appel de queue, où l'appel de récursion est transformé sous le capot en boucle de sorte qu'aucune taille de pile maximale atteinte erreur existe.

mais en javascript Les moteurs actuels ne supportent pas cela, il est prévu pour la nouvelle version du langage Ecmascript 6 .

Node.js a quelques options pour activer les fonctionnalités ES6, mais tail call n'est pas encore disponible.

afin que vous puissiez reformuler votre code pour mettre en œuvre une technique appelée trampoline , ou refactor afin de transformer la récursion en une boucle .

5
répondu Angular University 2017-05-23 12:02:53

si vous ne voulez pas implémenter votre propre wrapper, vous pouvez utiliser un système de file d'attente, par exemple async.la file d'attente , file d'attente .

1
répondu weakish 2014-12-05 10:52:54

en ce qui concerne l'augmentation de la taille maximale de la pile, sur les machines 32 bits et 64 bits, L'allocation de mémoire par défaut de V8 est respectivement de 700 MB et 1400 MB. Dans les versions plus récentes de V8, les limites de mémoire sur les systèmes 64 bits ne sont plus définies par V8, indiquant théoriquement aucune limite. Cependant, L'OS (Système D'exploitation) sur lequel le noeud est en cours d'exécution peut toujours limiter la quantité de mémoire V8 peut prendre, de sorte que la vraie limite de n'importe quel processus donné ne peut pas être généralement indiquée.

bien que V8 fasse disponible l'option --max_old_space_size , qui permet de contrôler la quantité de mémoire disponible pour un processus , en acceptant une valeur en MB. Si vous avez besoin d'augmenter l'allocation de mémoire, passez simplement cette option à la valeur désirée lors du frai D'un processus de Noeud.

c'est souvent une excellente stratégie pour réduire l'allocation de mémoire disponible pour une instance de Noeud donnée, surtout quand on exécute plusieurs instances. Comme pour les limites de cheminée, se demander si massive mémoire besoins sont mieux délégué à la couche de stockage, tel qu'une base de données en mémoire ou similaire.

0
répondu serkan 2015-10-28 16:40:25

veuillez vérifier que la fonction que vous importez et celle que vous avez déclarée dans le même fichier n'ont pas le même nom.

je vais vous donner un exemple de cette erreur. Dans express JS (utilisant ES6), envisager le scénario suivant:

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

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

le scénario ci-dessus causera infâme RangeError: la taille maximale de la pile d'appels excédé erreur parce que la fonction continue à s'appeler tant de fois qu'il exécute de maximale de la pile d'appel.

la Plupart du temps, l'erreur est dans le code (comme celle ci-dessus). Une autre façon de résoudre est d'augmenter manuellement la pile d'appels. Eh bien, cela fonctionne pour certains cas extrêmes, mais il n'est pas recommandé.

J'espère que ma réponse vous a aidé.

0
répondu Abhay Shiro 2017-12-14 20:08:25