How to get a backtrace from a SystemStackError: stack level too deep?

Ruby

Ruby 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestioniblueView Question on Stackoverflow
Solution 1 - RubyElliot NelsonView Answer on Stackoverflow
Solution 2 - RubyGerry GleasonView Answer on Stackoverflow
Solution 3 - RubyLinuxiosView Answer on Stackoverflow
Solution 4 - RubyJonathan SwartzView Answer on Stackoverflow
Solution 5 - RubyPaul AlexanderView Answer on Stackoverflow
Solution 6 - RubyAndrew GrimmView Answer on Stackoverflow
Solution 7 - RubyMartin VidnerView Answer on Stackoverflow
Solution 8 - RubymakdadView Answer on Stackoverflow