How to get a backtrace from a SystemStackError: stack level too deep?
RubyRuby Problem Overview
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?
Ruby Solutions
Solution 1 - Ruby
Another method for those finding this question later... An [excellent gist][1] 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()
[1]: https://gist.github.com/jbgo/4493822 "jbgo / debug_system_stack_error.md"
Solution 2 - Ruby
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.
Solution 3 - Ruby
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.
Solution 4 - Ruby
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
}
Solution 5 - Ruby
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
}
Solution 6 - Ruby
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.
Solution 7 - Ruby
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 `<main>'
Solution 8 - Ruby
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.