Saltar al contenido

Node.js: se excedió el tamaño máximo de la pila de llamadas

Agradeceríamos tu ayuda para difundir nuestras crónicas en referencia a las ciencias de la computación.

Solución:

Debe envolver su llamada de función recursiva en un

  • setTimeout,
  • setImmediate o
  • process.nextTick

función para dar a node.js la oportunidad de borrar la pila. Si no hace eso y hay muchos bucles sin ningún verdadero llamada de función asíncrona o si no espera la devolución de llamada, su RangeError: Maximum call stack size exceeded estarán inevitable.

Hay muchos artículos sobre “Posible bucle asíncrono”. Acá hay uno.

Ahora un poco más de código de ejemplo:

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

Esto es correcto:

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

Ahora su ciclo puede volverse demasiado lento, porque perdemos un poco de tiempo (un viaje de ida y vuelta del navegador) por ronda. Pero no tienes que llamar setTimeout en cada ronda. Normalmente está bien hacerlo cada 1000 veces. Pero esto puede diferir según el tamaño de su pila:

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

Encontré una solución sucia:

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

Simplemente aumenta el límite de la pila de llamadas. Creo que esto no es adecuado para el código de producción, pero lo necesitaba para el script que se ejecuta solo una vez.

En algunos idiomas, esto se puede resolver con la optimización de la llamada final, donde la llamada recursiva se transforma bajo el capó en un bucle, por lo que no existe un error de tamaño máximo de pila alcanzado.

Pero en javascript los motores actuales no soportan esto, está previsto para la nueva versión del lenguaje Ecmascript 6.

Node.js tiene algunos indicadores para habilitar las funciones de ES6, pero la llamada final aún no está disponible.

Por lo tanto, puede refactorizar su código para implementar una técnica llamada trampolín, o refactorizar para transformar la recursividad en un bucle.

Eres capaz de añadir valor a nuestra información cooperando tu experiencia en las aclaraciones.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *