Подтвердить что ты не робот

Как получить обратную трассировку из SystemStackError: уровень стека слишком глубок?

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

Пример

Для некоторых методов foo, bar и baz, которые вызывают друг друга в цикле:

def foo
  bar
end

def bar
  baz
end

def baz
  foo
end

foo

Когда я запускаю этот код, я просто получаю сообщение test.rb:6: stack level too deep (SystemStackError). Было бы полезно получить, по крайней мере, последние 100 строк стека, поэтому я мог сразу увидеть, что это цикл между foo, bar и baz, например:

test.rb:6: stack level too deep (SystemStackError)
  test.rb:2:in `foo'
  test.rb:10:in `baz'
  test.rb:6:in `bar'
  test.rb:2:in `foo'
  test.rb:10:in `baz'
  test.rb:6:in `bar'
  test.rb:2:in `foo'
  [...]

Есть ли способ сделать это?

EDIT:

Как вы можете видеть из приведенного ниже ответа, Рубиний может это сделать. К сожалению, некоторые ошибки rubinius помешают мне использовать его с программным обеспечением, которое я хотел бы отлаживать. Поэтому, если быть точным, вопрос:

Как получить обратную трассировку с помощью МРТ (по умолчанию ruby) 1.9?

4b9b3361

Ответ 1

По-видимому, это отслеживалось как функция 6216 и исправлено в Ruby 2.2.

$ ruby system-stack-error.rb
system-stack-error.rb:6:in `bar': stack level too deep (SystemStackError)
        from system-stack-error.rb:2:in `foo'
        from system-stack-error.rb:10:in `baz'
        from system-stack-error.rb:6:in `bar'
        from system-stack-error.rb:2:in `foo'
        from system-stack-error.rb:10:in `baz'
        from system-stack-error.rb:6:in `bar'
        from system-stack-error.rb:2:in `foo'
        from system-stack-error.rb:10:in `baz'
         ... 10067 levels...
        from system-stack-error.rb:10:in `baz'
        from system-stack-error.rb:6:in `bar'
        from system-stack-error.rb:2:in `foo'
        from system-stack-error.rb:13:in `<main>'

Ответ 2

Другой метод для тех, кто находит этот вопрос позже... отличный gist содержит инструкции по включению функции трассировки в консоли и печати всех вызовов функций в файл. Просто протестирован на 1.9.3-p194, и он отлично работал.

В том числе здесь, если сущность исчезнет когда-нибудь:

$enable_tracing = false
$trace_out = open('trace.txt', 'w')

set_trace_func proc { |event, file, line, id, binding, classname|
  if $enable_tracing && event == 'call'
    $trace_out.puts "#{file}:#{line} #{classname}##{id}"
  end
}

$enable_tracing = true
a_method_that_causes_infinite_recursion_in_a_not_obvious_way()

Ответ 3

Это была довольно неприятная проблема, которая время от времени возникала при отладке рубинов/рельсов. Я только что обнаружил работоспособную технику для обнаружения стека, растущего за пределами, прежде чем он разбил системный стек, а реальная обратная трасса потерялась или исказилась. Основной шаблон:

raise "crash me" if caller.length > 500

увеличивайте число 500 до тех пор, пока оно не будет срабатывать досрочно, и у вас будет хороший след вашей растущей проблемы с стеком.

Ответ 4

Здесь:

begin
  foo
rescue SystemStackError
  puts $!
  puts caller[0..100]
end

Метод Kernel#caller возвращает backtrace стека как массив, поэтому он печатает первые от 0 до 100 записей в обратном направлении. Поскольку caller можно вызвать в любое время (и использовать для некоторых довольно странных вещей), даже если Ruby не печатает backtrace для SystemStackErrors, вы все равно можете получить обратную трассировку.

Ответ 5

Объединяя предложения из нескольких ответов, это вызовет ошибку (со стеком трассировки), когда ваш стек вызовов станет слишком глубоким. Это замедлит все вызовы методов, поэтому вы должны попытаться установить их как можно ближе к тому, где вы думаете, что бесконечный цикл происходит, как вы можете.

set_trace_func proc {
  |event, file, line, id, binding, classname| 
  if event == "call"  && caller_locations.length > 500
    fail "stack level too deep"
  end
}

Ответ 6

Если вы используете pry, это фактически позволит вам проникнуть в цепочку вызовов метода, которая слишком глубоко.

set_trace_func proc { |event, file, line, id, proc_binding, classname|
  if !$pried && proc_binding && proc_binding.eval( "caller.size" ) > 200
    $pried = true
    proc_binding.pry
  end
}

Ответ 7

Вы можете получить такую ​​трассировку стека с Ruby 1.8. Если наличие синтаксиса стиля 1.9 (например, {foo: 42}) является единственной проблемой, тогда скомпилируйте заголовок Ruby 1.8.

Ответ 8

Я пробовал много вещей здесь, но не смог найти, где была рекурсия (я использую Ruby 2.0).

Затем я попробовал "тупое". Я запустил код проблемы (в моем случае, модульные тесты) в терминале, а затем нажал Ctrl-C, когда я думал, что атакующий тест запущен (помогли несколько звонков puts). Конечно, у меня есть полная обратная трассировка:)

У меня было полное 3 или 4 секунды, чтобы ответить на Macbook Pro 2013 года.