Wednesday, November 4, 2009

:nodoc: considered harmful

I may be weird in this, but I typically find that the best way to figure out how a Ruby gem works is to start picking through its RDoc. And for the most part, it couldn't be easier: do a gem install and hop over to http://localhost:8808/. It's good for an overview, it's good for reference. Being able to quickly scan an API, and then flick open the source when something isn't clear is great.

But then you find things like, say:

it_should_behave_like (*shared_example_groups)

Use this to pull in examples from shared example groups.
[show source]

# File lib/spec/example/example_group_methods.rb, line 64
def it_should_behave_like(*shared_example_groups)
shared_example_groups.each do |group|
include_shared_example_group(group)
end
end



What, exactly, does "include_shared_example_group" do? From context of the API, something like look up strings in a table or constants in the namespace and include the associated module. Something like that. What exactly, though? For that, you'll need to dig into the source itself, which, while it's hardly impossible, is a bit of a roadbump in using the gem.

And this is for Rspec, which I generally like, in its featuritis-addled way. (Heaven protect us from the raft of testing frameworks out there. Wouldn't it be better to contribute to a known way than to re-implement everything over and over?) Don't get me started with trying to follow the thread through the Rails/Active/Action sausage factory.

Ultimately, what's the benefit to anyone of using :nodoc:? Either you're trying to reduce clutter in your API documentation (in which case, :nodoc: is not a panacea; it's hardly an aspirin) or you're trying to hide something hideous from the world.

Wednesday, July 22, 2009

Percent Double Ewe

One of my favorite under-appreciated aspects of the Ruby syntax:


%w{an array of strings} #=> ["an", "array", "of", "strings"]


Two of my favorite uses of this construct:

File::join(%w{who cares what platform}) #=> "who/cares/what/platform"

Kernel::system *%w{sudo cat *} #=> [and the * doesn't glob - WYS is exactly WYG]


Am I the only one who does these things? Or is it just the Rails tide-pool that doesn't?

Thursday, July 16, 2009

Rake and task arguments

I'm putting this together because I really like Rake for the most part, but there is an irritating tendency for the docs to fall out of currency. And they like the #nodoc tag. A lot.

So, in more recent versions of Rake (0.8.6 or thereabouts), you can use arguments to tasks, like this:


task :my_task, [:whick, :whack] => [:depend_on] do |task, args|
args.with_defaults(:whack => 42)
end


(Actually, there's about a dozen (well, four) different formats for task definition, but there's 20 reasonable formats that won't work, and I've found that the :task, [:args] => [:deps] format is the best one to remember.)

Now, if a dependency also has arguments, and they're a subset of the primary tasks arguments, then they'll be assigned, regardless of their relative order.

task :depend_on, [:whack] do; end

:depend_on will get :whack assigned from the arguments assigned by :my_task.

Wednesday, April 15, 2009

DRb and RangeError

I almost decided this was so obscure that it wasn't worth writing up, but in the short foray in to Googleland to find an answer, I found that other people (both of them) were having the same problem, and one of them was working with Rails, so maybe someone else might like to understand this issue.

Here's what happens:
You're using DRb. You've got a remote object, but when you try to reference it, ruby blows up with:

RangeError: 0xcabbage is recycled object

Now, possibly what's happened is that you're trying to reference an object that's fallen out of scope in it's native process, and it's been garbage collected. That's most likely, really. But you can check to see if that object is still live in the native interpreter to rule that out.

What I'd done was a little different: I'd started DRb, then forked. From the forked process, I was trying to start a child process. When that child (that's three processes deep now) tried to access a object in the forked parent, I was getting RangeErrors. Very confusing.

Especially since the DRb URIs matched up. And the DRb refs. And the parent's object that was supposedly "recycled" was surviving after the child had failed. And I'd stopped DRb and restarted it in the forked parent process.
But then I started to dig into what DRb does when you call stop_service. Have a gander:


if Thread.current['DRb'] && Thread.current['DRb']['server'] == self
Thread.current['DRb']['stop_service'] = true
else
@thread.kill
end

Most poignantly, if we're in the DRb servers main loop thread, we wait until we're done with a loop before we quit the server. Which makes sense. You don't want to drop the current request on the floor. (Plus, it's good use of thread local variables, to boot.) But since the forked parent was launched in the context of a DRb request, when the fork occured, it's main thread was the loop thread for DRb.

So, what happens is that the forked parent gets a DRb server with the same URI as its parent. So when its child tries to connect back to an object created in the forked parent, it's actually asking the forked parent's parent, which has no idea about this object.

Solution: create a new thread, restart DRb in the thread. The DRb in the forked parent can die now, and a new DRbServer gets created, and now it's children talk to it. Voila

Hm. Rereading that, I suspect a diagram might help. Maybe if anyone ever cares about this post, I'll knock one together.

Monday, March 9, 2009

Quickie for debugging

I find that I write scripts that have to absorb exceptions more often than I'd expect. This occasionally makes life difficult when I know there's an exception being raised, and I have some idea where, but not what it is. In the past, I'd use a tried and true instrumentation of the form:

begin
troubled_object.do_irritating_thing(:frequency => :irregularly)
rescue Object => ex
puts "#{ex.class}: #{ex.message}\n#{ex.backtrace.join($/)}"
end


Which is also the most concise way I know to generally evade Ruby's contraction of backtraces or the various backtrace trimming routines. I use it frequently enough that I keep a Kernel method snippet around for the odd times it gets too cumbersome.

But the other day it occurred to me, that especially for quick debugging there's an alternative approach:

troubled_objects_client.call_trouble rescue p $!