Узел.Яш - максимальный размер стека вызовов превысило


когда я запускаю свой код, узел.Яш бросить "RangeError: Maximum call stack size exceeded" исключение, вызванное слишком большим количеством вызовов рекурсии. Я попытался увеличить узел.JS stack-size by sudo node --stack-size=16000 app, но узел.JS сбой без какого-либо сообщения об ошибке. Когда я запускаю это снова без sudo, то узел.печать на JS 'Segmentation fault: 11'. Есть ли возможность решить эту проблему без удаления рекурсии вызова?

спасибо

6 62

6 ответов:

вы должны обернуть свой рекурсивный вызов функции в

  • setTimeout,
  • setImmediate или
  • process.nextTick

функция для того чтобы дать узел.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 в каждом раунде. Обычно это o. k., чтобы сделать это каждый 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.

узел.js имеет некоторые флаги для включения функций ES6, но хвостовой вызов еще не доступен.

таким образом, вы можете рефакторинг кода реализуйте метод под названием Прыжки на батуте, или рефакторинг для того, чтобы преобразование рекурсии в цикл.

Если вы не хотите реализовывать свою собственную оболочку, вы можете использовать систему очередей, например асинхронность.очередь,очереди.

Что касается увеличения максимального размера стека, то на 32-битных и 64-битных машинах V8 значения по умолчанию для выделения памяти составляют соответственно 700 МБ и 1400 МБ. В более новых версиях V8 ограничения памяти на 64-разрядных системах больше не устанавливаются V8, теоретически указывая на отсутствие ограничений. Однако ОС (Операционная система), на которой работает узел, всегда может ограничить объем памяти, который может занять V8, поэтому истинный предел любого данного процесса не может быть вообще заявлен.

хотя V8 делает доступным --max_old_space_size опция, которая позволяет контролировать объем памяти, доступной для

убедитесь, что импортируемая функция и функция, объявленная в том же файле, имеют разные имена.

Я приведу вам пример этой ошибки. В express JS (с помощью ES6) рассмотрим следующий сценарий:

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

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

приведенный выше сценарий вызовет печально известный RangeError: максимальный размер стека вызовов превысило ошибка, потому что функция продолжает вызывать себя так много раз, что она работает из максимального стека вызовов.

в большинстве случаев ошибка находится в коде (как и выше). Другой способ решения-вручную увеличить стек вызовов. Ну, это работает для некоторых крайних случаев, но это не рекомендуется.

надеюсь, что мой ответ помог вам.