可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Often I get hard to debug infinite recursions when coding ruby. Is there a way to get a backtrace out of a SystemStackError
to find out, where exactly the infinite loop occurs?
Example
Given some methods foo
, bar
and baz
which call each other in a loop:
def foo bar end def bar baz end def baz foo end foo
When I run this code, I just get the message test.rb:6: stack level too deep (SystemStackError)
. It would be useful to get at least the last 100 lines of the stack, so I could immediately see this is a loop between foo
, bar
and baz
, like this:
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' [...]
Is there any way to accomplish this?
EDIT:
As you may see from the answer below, Rubinius can do it. Unfortunately some rubinius bugs prevent me from using it with the software I'd like to debug. So to be precise the question is:
How do I get a backtrace with MRI (the default ruby) 1.9?
回答1:
Apparently this was tracked as feature 6216 and fixed in 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 `'
回答2:
Another method for those finding this question later... An excellent gist provides instructions on enabling a trace function in the console and printing all function calls to a file. Just tested on 1.9.3-p194 and it worked great.
Including here in case the gist goes away someday:
$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:
This has been a somewhat vexing problem that I've had from time to time in debugging ruby/rails. I just discovered a workable technique to detect the stack growing out of bounds before it crashed the system stack and the real backtrace gets lost or garbled. The basic pattern is:
raise "crash me" if caller.length > 500
bump up the 500 until it doesn't fire prematurely and you will have a nice trace of your growing stack problem.
回答4:
Here:
begin foo rescue SystemStackError puts $! puts caller[0..100] end
The method Kernel#caller
returns a stack backtrace as an array, so this prints the first 0 to 100 entries in the backtrace. Because caller
can be called at any time (and used for some pretty weird things) even if Ruby doesn't print backtrace for SystemStackErrors, you can still get a backtrace.
回答5:
Combining suggestions from several answers, this will throw an error (with stack trace) when your call stack gets too deep. This will slow down all method calls, so you should try to put it as close to where you think the infinite loop is happening as you can.
set_trace_func proc { |event, file, line, id, binding, classname| if event == "call" && caller_locations.length > 500 fail "stack level too deep" end }
回答6:
If you happen to use pry, this will actually let you break into the method call chain that's gone too deep.
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:
You can get this kind of stack trace with Ruby 1.8. If the presence of 1.9 style syntax (eg {foo: 42}
) is the only issue, then compile Ruby 1.8 head.
回答8:
I tried many of the things here, but could not find where the recursion was (I am using Ruby 2.0).
Then, I tried "the stupid thing". I ran the problem code (in my case, unit tests) in Terminal, and then pressed Ctrl-C
when I thought the offensive test was running (a few puts
calls helped). Sure enough, I got a full backtrace :)
I had a full 3 or 4 seconds to respond on a 2013 Macbook Pro.