【问题标题】:How to get a backtrace from a SystemStackError: stack level too deep?如何从 SystemStackError 中获取回溯:堆栈级别太深?
【发布时间】:2012-07-18 15:05:13
【问题描述】:

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

示例

给定一些方法foobarbaz在循环中相互调用:

def foo
  bar
end

def bar
  baz
end

def baz
  foo
end

foo

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

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 bugs 阻止我将它与我想调试的软件一起使用。所以准确来说问题是:

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

【问题讨论】:

    标签: ruby


    【解决方案1】:

    稍后发现此问题的另一种方法...excellent 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()
    

    【讨论】:

    • 结合@gerry-gleason 的提示,这对我来说特别有效。通过引发异常,我不会对非递归调用感到不知所措。
    • 嗯,非常愚蠢,当您最需要它时无法获得有意义的堆栈跟踪...谢谢,这对我有用
    • 将此答案与我下面的答案结合起来,测试堆栈深度并在某个限制处引发异常,您就有了一种自动检测。哦,这是它自己的答案,我会投票赞成。
    【解决方案2】:

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

    raise "crash me" if caller.length > 500
    

    提高 500 直到它不会过早触发,这样您就可以很好地跟踪不断增长的堆栈问题。

    【讨论】:

    • 问题是放在哪里。你不能把它放在程序的每一步。您也许可以在引发错误后通过反复试验来确定位置,但您无法提前在代码中找到它。
    • 仍然是一种更快的查找方法 :)
    • 使用caller_locations.length 效率更高,尤其是在您使用@elliot-nelson 的跟踪技术时。
    • sawa - 将其放入 set_trace_func 过程中,并用 if 语句包围 - 'if event == "call" && caller.length > 500'
    【解决方案3】:

    这里:

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

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

    【讨论】:

    • 不适用于我的 ruby​​ 1.9.3-p194,代码如下:gist.github.com/3136778。它只输出recursion.rb:6: stack level too deep (SystemStackError)。你自己试过吗?您使用的是哪个 ruby​​ 版本?
    • @iblue:见编辑。那应该解决它。给我一个堆栈跟踪,告诉我从哪里调用 foo,而我的 foo 只是 def foo; foo; end
    • 仍然不打印包含任何方法 foo、bar 或 baz 的回溯。它输出 stack level too deeprecursion.rb:13:in
      '`,这是 begin 块开始的行。没有帮助。
    • @iblue:那么我怀疑任何事情都会有所帮助。我将删除答案,以便问题再次无人回答。
    • 然后我得到了异常的来源(recursion.rb:6),但仍然没有回溯。
    【解决方案4】:

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

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

    【讨论】:

    • 下面还建议,您可以将此更改添加到答案中以使其更快一点:使用 caller_locations.length 更有效,特别是如果您使用@elliot-nelson 的跟踪技术
    【解决方案5】:

    如果你碰巧使用了 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
    }
    

    【讨论】:

    • 有趣,没试过,但是很有趣的调试技术
    • 所以,这与上面的类似,但是您最终会像在断点处停止一样进入 pry 会话? (相对于抛出异常)
    【解决方案6】:

    显然这被跟踪为feature 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.3.1,但它什么也没做......我错过了什么吗?
    • 昨天我有一个堆栈错误并尝试了它。没啥事儿。然后我解决了这个问题,然后再次尝试查看并得到一个没有这样的文件或目录 LoadError。
    • 我实际上遇到了同样的问题,无论是 2.3.x 还是 2.4.3(在 Windows 上)。我还尝试捕获异常并查看它的backtrace,但出于某种奇怪的原因,它给出了nil
    【解决方案7】:

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

    【讨论】:

    • 您能否详细说明为什么这在 1.9 中不起作用?我找不到任何其他关于这种情况或为什么...
    • @bjeanes 我不太记得了,但我可能一直在想 assert_nothing_raised 在 1.8 和 1.9 之间的行为有何不同,这可能是测试/单元与最小测试的区别。
    【解决方案8】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-10
      • 2013-10-05
      • 2014-10-25
      • 2017-08-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多