50

在编写 ruby​​ 代码时,我经常很难调试无限递归。有没有办法从 a 中获取回溯SystemStackError以找出无限循环到底发生在哪里?

例子

给定一些方法foo,它们在循环barbaz相互调用:

def foo
  bar
end

def bar
  baz
end

def baz
  foo
end

foo

当我运行此代码时,我只会收到消息test.rb:6: stack level too deep (SystemStackError)。至少获取堆栈的最后 100 行会很有用,所以我可以立即看到这是一个 和 之间的循环,foo如下所示:barbaz

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'
  [...]

有没有办法做到这一点?

编辑:

正如您从下面的答案中看到的那样,Rubinius 可以做到。不幸的是,一些rubinius 错误阻止我将它与我想调试的软件一起使用。所以准确地说,问题是:

如何使用 MRI(默认 ruby​​)1.9 获得回溯?

4

8 回答 8

43

稍后发现此问题的人的另一种方法...一个出色的要点提供了有关在控制台中启用跟踪功能并将所有函数调用打印到文件的说明。刚刚在 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()
于 2014-02-05T18:52:40.287 回答
20

这是我在调试 ruby​​/rails 时不时遇到的一个有点令人烦恼的问题。我刚刚发现了一种可行的技术,可以在堆栈崩溃系统堆栈并且真正的回溯丢失或乱码之前检测堆栈超出范围。基本模式是:

raise "crash me" if caller.length > 500

提高 500 直到它不会过早触发,并且您将很好地跟踪您不断增长的堆栈问题。

于 2012-11-13T15:03:42.147 回答
14

这里:

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

该方法Kernel#caller将堆栈回溯作为数组返回,因此这将打印回溯中的前 0 到 100 个条目。因为caller可以随时调用(并用于一些非常奇怪的事情)即使 Ruby 不打印 SystemStackErrors 的回溯,您仍然可以获得回溯。

于 2012-07-18T15:08:37.643 回答
6

结合来自多个答案的建议,当您的调用堆栈太深时,这将引发错误(带有堆栈跟踪)。这将减慢所有方法调用,因此您应该尽量将其放置在您认为无限循环发生的位置附近。

set_trace_func proc {
  |event, file, line, id, binding, classname| 
  if event == "call"  && caller_locations.length > 500
    fail "stack level too deep"
  end
}
于 2015-02-23T17:47:32.960 回答
4

如果你碰巧使用了 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
}
于 2014-08-28T20:42:47.663 回答
3

您可以使用 Ruby 1.8 获得这种堆栈跟踪。如果 1.9 样式语法(例如{foo: 42})的存在是唯一的问题,那么编译Ruby 1.8 head

于 2012-07-18T23:14:44.670 回答
3

显然这被跟踪为功能 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>'
于 2015-08-05T14:13:15.007 回答
1

我在这里尝试了很多东西,但找不到递归在哪里(我使用的是 Ruby 2.0)。

然后,我尝试了“愚蠢的事情”。我在终端中运行了问题代码(在我的例子中是单元测试),然后Ctrl-C当我认为攻击性测试正在运行时按下(几个puts调用有帮助)。果然,我得到了完整的回溯:)

在 2013 年的 Macbook Pro 上,我有整整 3 或 4 秒的时间来响应。

于 2014-05-01T16:50:40.677 回答