Cuando ejecuto mi código, Node.js lanza una "RangeError: Maximum call stack size exceeded"
excepción causada por demasiadas llamadas recursivas. Intenté aumentar el tamaño de la pila de Node.js sudo node --stack-size=16000 app
, pero Node.js se bloquea sin ningún mensaje de error. Cuando vuelvo a ejecutar esto sin sudo, Node.js imprime 'Segmentation fault: 11'
. ¿Existe la posibilidad de resolver esto sin eliminar mis llamadas recursivas?
node.js
recursion
stack-overflow
callstack
usuario1518183
fuente
fuente
Segmentation fault: 11
generalmente significa un error en el nodo.Respuestas:
Debes envolver tu llamada de función recursiva en un
setTimeout
,setImmediate
oprocess.nextTick
función para darle a node.js la oportunidad de borrar la pila. Si no hace eso y hay muchos bucles sin ninguna llamada de función asíncrona real o si no espera la devolución de llamada,
RangeError: Maximum call stack size exceeded
será inevitable .Hay muchos artículos sobre el "bucle asincrónico potencial". Aquí tienes uno .
Ahora un código de ejemplo más:
// 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 al navegador) por ronda. Pero no tiene que llamar
setTimeout
en todas las rondas. Normalmente está bien hacerlo cada milésima vez. 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 ... });
fuente
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 un script que se ejecuta solo una vez.
fuente
En algunos lenguajes, esto se puede resolver con la optimización de la llamada de cola, donde la llamada de recursividad se transforma bajo el capó en un bucle para que no exista un error de tamaño máximo de pila alcanzado.
Pero en javascript los motores actuales no admiten esto, está previsto para una nueva versión del lenguaje Ecmascript 6 .
Node.js tiene algunos indicadores para habilitar las funciones de ES6, pero la llamada de cola 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 .
fuente
Tuve un problema similar a este. Tuve un problema con el uso de varios Array.map () en una fila (alrededor de 8 mapas a la vez) y estaba obteniendo un error maximum_call_stack_exceeded. Resolví esto cambiando el mapa en bucles 'for'
Entonces, si está utilizando muchas llamadas de mapa, cambiarlas a bucles for puede solucionar el problema
Editar
Solo para mayor claridad y probablemente información no necesaria pero buena para saber, el uso
.map()
hace que la matriz se prepare (resolviendo captadores, etc.) y la devolución de llamada se almacene en caché, y también mantiene internamente un índice de la matriz ( por lo que la devolución de llamada se proporciona con el índice / valor correcto). Esto se acumula con cada llamada anidada, y se recomienda precaución cuando no está anidada también, como la siguiente.map()
podría llamarse antes de que la primera matriz sea recolectada como basura (si es que lo hace).Toma este ejemplo:
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 })
Si cambiamos esto a:
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 }
Espero que esto tenga algún sentido (no tengo la mejor forma de usar las palabras) y ayude a algunos a evitar el rascado de cabeza por el que pasé.
Si alguien está interesado, aquí también hay una prueba de rendimiento que compara el mapa y los bucles for (no es mi trabajo).
https://github.com/dg92/Performance-Analysis-JS
Los bucles for suelen ser mejores que los mapas, pero no reducen, filtran ni encuentran
fuente
Pre:
para mí, el programa con la pila de llamadas Max no se debió a mi código. Terminó siendo un problema diferente lo que provocó la congestión en el flujo de la aplicación. Entonces, debido a que estaba tratando de agregar demasiados elementos a mongoDB sin ninguna posibilidad de configuración, el problema de la pila de llamadas estaba apareciendo y me tomó unos días descubrir qué estaba pasando ... eso decía:
Siguiendo con lo que respondió @Jeff Lowery: Disfruté mucho esta respuesta y aceleró el proceso de lo que estaba haciendo al menos 10 veces.
Soy nuevo en la programación, pero intenté modularizar la respuesta. Además, no me gustó que se lanzara el error, así que lo envolví en un bucle do while. Si algo de lo que hice es incorrecto, no dude en corregirme.
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); };
Echa un vistazo a esta esencia para ver mis archivos y cómo llamar al bucle. https://gist.github.com/gngenius02/3c842e5f46d151f730b012037ecd596c
fuente
Si no desea implementar su propio contenedor, puede usar un sistema de cola, por ejemplo , async.queue , queue .
fuente
Pensé en otro enfoque que usa referencias de funciones que limitan el tamaño de la pila de llamadas sin usar
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; }
salida:
$ 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)
fuente
Con respecto al aumento del tamaño de pila máximo, en máquinas de 32 y 64 bits, los valores predeterminados de asignación de memoria de V8 son, respectivamente, 700 MB y 1400 MB. En las versiones más recientes de V8, los límites de memoria en los sistemas de 64 bits ya no están establecidos por V8, lo que teóricamente indica que no hay límite. Sin embargo, el SO (sistema operativo) en el que se ejecuta Node siempre puede limitar la cantidad de memoria que puede ocupar V8, por lo que el límite real de cualquier proceso dado no se puede establecer de forma general.
Aunque V8 pone a disposición la
--max_old_space_size
opción, que permite controlar la cantidad de memoria disponible para un proceso , aceptando un valor en MB. Si necesita aumentar la asignación de memoria, simplemente pase a esta opción el valor deseado al generar un proceso de nodo.A menudo, es una excelente estrategia reducir la asignación de memoria disponible para una instancia de Nodo determinada, especialmente cuando se ejecutan muchas instancias. Al igual que con los límites de pila, considere si las necesidades de memoria masiva se delegan mejor a una capa de almacenamiento dedicada, como una base de datos en memoria o similar.
fuente
Compruebe que la función que está importando y la que ha declarado en el mismo archivo no tienen el mismo nombre.
Les daré un ejemplo de este error. En Express JS (usando ES6), considere el siguiente escenario:
import {getAllCall} from '../../services/calls'; let getAllCall = () => { return getAllCall().then(res => { //do something here }) } module.exports = { getAllCall }
El escenario anterior causará el infame RangeError: el tamaño máximo de la pila de llamadas excedió el error porque la función sigue llamándose a sí misma tantas veces que se agota la pila máxima de llamadas.
La mayoría de las veces, el error está en el código (como el anterior). Otra forma de resolverlo es aumentar manualmente la pila de llamadas. Bueno, esto funciona para ciertos casos extremos, pero no se recomienda.
Espero que mi respuesta te haya ayudado.
fuente
Puede usar loop for.
var items = {1, 2, 3} for(var i = 0; i < items.length; i++) { if(i == items.length - 1) { res.ok(i); } }
fuente
var items = {1, 2, 3}
no es una sintaxis JS válida. ¿Cómo se relaciona esto con la pregunta?