Когда я запускаю свой код, Node.js выдает "RangeError: Maximum call stack size exceeded"
исключение, вызванное слишком большим количеством рекурсивных вызовов. Я попытался увеличить размер стека Node.js на sudo node --stack-size=16000 app
, но Node.js вылетает без сообщения об ошибке. Когда я запускаю этот раз без Суда, то Node.js отпечатков 'Segmentation fault: 11'
. Есть ли возможность решить эту проблему, не удаляя мои рекурсивные вызовы?
node.js
recursion
stack-overflow
callstack
user1518183
источник
источник
Segmentation fault: 11
обычно означает ошибку в узле.Ответы:
Вы должны заключить рекурсивный вызов функции в
setTimeout
,setImmediate
или жеprocess.nextTick
чтобы дать node.js возможность очистить стек. Если вы этого не сделаете и есть много циклов без какого-либо реального вызова асинхронной функции или если вы не дождетесь обратного вызова,
RangeError: Maximum call stack size exceeded
это будет неизбежно .Есть много статей о «Возможном асинхронном цикле». Вот один .
А теперь еще несколько примеров кода:
// 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 ... });
Это правильно:
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 ... });
Теперь ваш цикл может стать слишком медленным, потому что мы теряем немного времени (один обход браузера) на раунд. Но необязательно делать колл
setTimeout
в каждом раунде. Обычно это нормально делать это каждые 1000 раз. Но это может отличаться в зависимости от размера вашего стека: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 ... });
источник
Нашел грязное решение:
/bin/bash -c "ulimit -s 65500; exec /usr/local/bin/node --stack-size=65500 /path/to/app.js"
Это просто увеличивает лимит стека вызовов. Я думаю, что это не подходит для производственного кода, но мне это нужно для скрипта, который запускается только один раз.
источник
В некоторых языках это может быть решено с помощью оптимизации хвостового вызова, когда рекурсивный вызов преобразуется под капотом в цикл, поэтому ошибки достижения максимального размера стека не существует.
Но в javascript текущие движки этого не поддерживают, это предусмотрено для новой версии языка Ecmascript 6 .
В Node.js есть несколько флагов для включения функций ES6, но хвостовой вызов пока недоступен.
Таким образом, вы можете реорганизовать свой код, чтобы реализовать технику, называемую трамплином , или рефакторинг, чтобы преобразовать рекурсию в цикл .
источник
У меня была аналогичная проблема. У меня возникла проблема с использованием нескольких Array.map () подряд (около 8 карт одновременно), и я получал ошибку maximum_call_stack_exceeded. Я решил это, изменив карту на циклы for
Поэтому, если вы используете много вызовов карты, их изменение на циклы for может решить проблему.
редактировать
Просто для ясности и, возможно, не нужной, но полезной информации, использование
.map()
вызывает подготовку массива (разрешение геттеров и т. Д.) И кэширование обратного вызова, а также внутреннее сохранение индекса массива ( поэтому обратному вызову предоставляется правильный индекс / значение). Этот стек складывается с каждым вложенным вызовом, и рекомендуется соблюдать осторожность, когда он не вложен, поскольку следующий.map()
может быть вызван до того, как первый массив будет собран сборщиком мусора (если вообще будет).Вот пример:
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 })
Если мы изменим это на:
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 }
Я надеюсь, что в этом есть какой-то смысл (у меня не лучший способ использовать слова) и помогает некоторым избежать царапин на голове, через которые я прошел
Если кому-то интересно, вот еще тест производительности, сравнивающий карту и циклы (не моя работа).
https://github.com/dg92/Performance-Analysis-JS
Циклы For обычно лучше, чем map, но не сокращают, фильтруют или находят
источник
Предварительно:
для меня программа со стеком вызовов Max не была из-за моего кода. В конечном итоге это была другая проблема, которая вызвала перегрузку потока приложения. Так как я пытался добавить слишком много элементов в mongoDB без какой-либо конфигурации, скорее всего возникла проблема со стеком вызовов, и мне потребовалось несколько дней, чтобы понять, что происходит ...
В продолжение того, что ответил @Jeff Lowy: мне очень понравился этот ответ, и он ускорил процесс того, что я делал, по крайней мере, в 10 раз.
Я новичок в программировании, но я попытался разбить ответ на модули. Кроме того, мне не понравилось, что выдается ошибка, поэтому я вместо этого заключил ее в цикл do while. Если что-то, что я сделал, неправильно, пожалуйста, поправьте меня.
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); };
Ознакомьтесь с этой сутью, чтобы увидеть мои файлы и как вызвать цикл. https://gist.github.com/gngenius02/3c842e5f46d151f730b012037ecd596c
источник
Если вы не хотите реализовывать свою собственную оболочку, вы можете использовать систему очередей, например async.queue , queue .
источник
Я подумал о другом подходе с использованием ссылок на функции, который ограничивает размер стека вызовов без использования
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; }
вывод:
$ 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)
источник
Что касается увеличения максимального размера стека, то на 32-битных и 64-битных машинах значения по умолчанию для выделения памяти V8 составляют 700 МБ и 1400 МБ соответственно. В более новых версиях V8 ограничения памяти в 64-битных системах больше не устанавливаются V8, что теоретически означает отсутствие ограничений. Однако ОС (операционная система), в которой работает Node, всегда может ограничить объем памяти, который может занять V8, поэтому истинный предел любого данного процесса не может быть определен в общих чертах.
Хотя V8 предоставляет
--max_old_space_size
опцию, которая позволяет контролировать объем памяти, доступной процессу , принимая значение в МБ. Если вам нужно увеличить объем памяти, просто передайте этой опции желаемое значение при порождении процесса Node.Часто это отличная стратегия для уменьшения объема доступной памяти для данного экземпляра узла, особенно при запуске множества экземпляров. Как и в случае с ограничениями стека, подумайте, не лучше ли делегировать большие потребности в памяти на выделенный уровень хранения, такой как база данных в памяти или аналогичный.
источник
Убедитесь, что у функции, которую вы импортируете, и функции, которую вы объявили в одном файле, разные имена.
Я приведу вам пример этой ошибки. В экспресс-JS (с использованием ES6) рассмотрим следующий сценарий:
import {getAllCall} from '../../services/calls'; let getAllCall = () => { return getAllCall().then(res => { //do something here }) } module.exports = { getAllCall }
Вышеупомянутый сценарий вызовет печально известную ошибку RangeError: Превышен максимальный размер стека вызовов, потому что функция продолжает вызывать себя так много раз, что у нее заканчивается максимальный стек вызовов.
В большинстве случаев ошибка заключается в коде (например, выше). Другой способ решения - вручную увеличить стек вызовов. Что ж, это работает в некоторых крайних случаях, но не рекомендуется.
Надеюсь, мой ответ вам помог.
источник
Вы можете использовать цикл для.
var items = {1, 2, 3} for(var i = 0; i < items.length; i++) { if(i == items.length - 1) { res.ok(i); } }
источник
var items = {1, 2, 3}
не является допустимым синтаксисом JS. как это вообще связано с вопросом?