The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Keep Backtraces Honest: Set Forwardable::debug

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
john hume

Posts: 82
Nickname: duelnmrkrs
Registered: Oct, 2005

John Hume is a developer and consultant with ThoughtWorks.
Keep Backtraces Honest: Set Forwardable::debug Posted: Sep 12, 2008 5:28 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by john hume.
Original Post: Keep Backtraces Honest: Set Forwardable::debug
Feed Title: El Humidor
Feed URL: http://feeds.feedburner.com/ElHumidor
Feed Description: John D. Hume on Ruby and development with agile teams.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by john hume
Latest Posts From El Humidor

Advertisement

If you use Forwardable at all, you may have noticed that when you misspell or forget to implement something, the backtraces can be a little baffling. Take this example.

require 'forwardable'

class Foo
  extend Forwardable
  def_delegator :a, :bar # a is not defined
  def_delegator :b, :baz # b is defined but returns nil
  def b; end
end

f = Foo.new

When you call f.bar, you'll get an unsurprising NameError: undefined local variable or method 'a' for #<Foo:0x1044fec>. The backtrace will point at the line where you called bar, which is a little weird, but since that line doesn't have any mention of 'a' on it, you'll probably know to go looking for bar and discover the missing (or misspelled) delegate accessor.

When you call f.baz, you'll get an unsurprising NoMethodError: undefined method 'baz' for nil:NilClass. But, again, the backtrace will point at the line where you called baz, and here you're much more likely to go chasing down the wrong problem. If you really created your own instance of Foo just before that line, you're probably not going to worry that Foo.new returned nil. But what if you'd gotten that instance of Foo from some other call? The backtrace suggests that other call returned nil.

It's a dirty lie!

You have a perfectly good instance of Foo. The real problem is that its delegate accessor returned nil.

So why does the backtrace lie?

The first time I ran into this, I assumed Forwardable was implemented using some weird native magic that kept it from appearing in the backtrace. That seemed odd, since it doesn't do anything you couldn't do from normal Ruby code, but I didn't have time to dig into it.

When I finally did, I was surprised to find that it's normal Ruby code, but the method it writes (via module_eval) is (as interpolated for this example):

def baz(*args, &block)
  begin
    b.__send__(:baz, *args, &block)
  rescue Exception
    $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable::debug
    Kernel::raise
  end
end

As you'll have guessed, "(__FORWARDABLE__)" is passed as the file name to module_eval, so Forwardable's default behavior is to delete itself from backtraces, potentially making them misleading and wasting a lot of debugging time.

I don't know why it does that, but thankfully the authors realized you may not want that and made the hiding conditional on Forwardable::debug being false.

I highly recommend that any application using Forwardable has some bootstrapping code set that flag.

Forwardable.debug = true

Read: Keep Backtraces Honest: Set Forwardable::debug

Topic: MySQL is just a toy Previous Topic   Next Topic Topic: universal cat redux

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use