Tuesday, April 26, 2011

SPDY Gem and Ruby 1.8.7

‹prev | My Chain | next›

After last night's no-go with mod_spdy, I quickly move onto the SPDY gem. I still have a weak grasp on SPDY, so I want to stick as close as possible to my comfort zone during my initial exploration. For me, that means ruby.

Luckily, Ilya Grigorik has already done pioneering here.

First up, I fork his repo on github. I would not normally do that while exploring a gem, but, I need to make changes if I hope to realize the scope of my chain. So, I clone my forked repository down to my local machine:
➜  repos  git clone git@github.com:eee-c/spdy.git                                                                                             Cloning into spdy...

remote: Counting objects: 213, done.
remote: Compressing objects: 100% (107/107), done.
remote: Total 213 (delta 109), reused 181 (delta 93)
Receiving objects: 100% (213/213), 25.58 KiB, done.
Resolving deltas: 100% (109/109), done.
In the gem, I see that Ilya has a Gemfile for bundler:
➜  spdy git:(master) ✗ ls
examples Gemfile lib Rakefile README.md spdy.gemspec spec
Nice. I like to use rvm to keep my gem environments clean, so I create a .rvmrc file:
rvm use default@spdy
Once I execute that, I can bundle install the prerequisites:
➜  spdy git:(master) ✗ bundle install
Fetching source index for http://rubygems.org/
Installing rake (0.8.7)
Installing bindata (1.3.1)
Installing diff-lcs (1.1.2)
Installing ffi (1.0.7) with native extensions
Installing ffi-zlib (0.2.0)
Installing rspec-core (2.5.1)
Installing rspec-expectations (2.5.0)
Installing rspec-mocks (2.5.0)
Installing rspec (2.5.0)
Using spdy (0.0.2) from source at /home/cstrom/repos/spdy
Using bundler (1.0.9)
Your bundle is complete! It was installed into /home/cstrom/.rvm/gems/ree-1.8.7-2010.02@spdy
Nice! Working with gems is a pleasure in a gem like this.

I do notice that I am using ruby enterprise enterprise edition (1.8.7 and not 1.9) as my default ruby. I do not know how that happened, but I need to keep that in mind in case anything goes wrong.

Since there are specs, I think I'll start with some of them:
➜  spdy git:(master) rspec ./spec/protocol_spec.rb -cfs
/home/cstrom/repos/spdy/lib/spdy/protocol.rb:43: field 'type' shadows an existing method in SPDY::Protocol::Control::Header (NameError)
from /home/cstrom/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /home/cstrom/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /home/cstrom/repos/spdy/lib/spdy.rb:4
from /home/cstrom/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /home/cstrom/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /home/cstrom/repos/spdy/spec/helper.rb:2
from /home/cstrom/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /home/cstrom/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /home/cstrom/repos/spdy/spec/protocol_spec.rb:1
...
Yikes!

I am not familiar with "shadows an existing method in", but Google suggests that the bindata gem might be the culprit.

Since I have nothing but gems related to SPDY installed in my rvm gemset, I can ack-grep through all of my gems for the term "shadows":
➜  gems  ack shadows                          
bindata-1.3.1/lib/bindata/dsl.rb
254: if name_shadows_method?(name)
255: dsl_raise NameError.new("", name), "field '#{name}' shadows an existing method"
295: def name_shadows_method?(name)

...

bindata-1.3.1/spec/struct_spec.rb
29: it "should fail when field name shadows an existing method" do
Sure enough, this is the bindata gem.

Before trying anything else, let's see if this is a ruby 1.8.7 vs 1.9.2 issue:
➜  spdy git:(master) rvm use 1.9.2@spdy
Using /home/cstrom/.rvm/gems/ruby-1.9.2-p0 with gemset spdy
➜ spdy git:(master) gem install bundler
Successfully installed bundler-1.0.12
1 gem installed
➜ spdy git:(master) bundle install
Fetching source index for http://rubygems.org/
Installing rake (0.8.7)
Installing bindata (1.3.1)
Installing diff-lcs (1.1.2)
Installing ffi (1.0.7) with native extensions
Installing ffi-zlib (0.2.0)
Installing rspec-core (2.5.1)
Installing rspec-expectations (2.5.0)
Installing rspec-mocks (2.5.0)
Installing rspec (2.5.0)
Using spdy (0.0.2) from source at .
Using bundler (1.0.12)
Your bundle is complete! It was installed into /home/cstrom/.rvm/gems/ruby-1.9.2-p0@spdy

➜ spdy git:(master) rspec ./spec/protocol_spec.rb -cfs

SPDY::Protocol
NV
should create an NV packet
SYN_STREAM
should create a SYN_STREAM packet
should parse SYN_STREAM packet
SYN_REPLY
should create a SYN_REPLY packet
should parse SYN_REPLY packet
DATA
should create a data frame
should create a FIN data frame
should read a FIN data frame

Finished in 0.02769 seconds
8 examples, 0 failures
Yup, it works just fine in 1.9.

I could just move on with 1.9.2, but I opt to investigate why this is not working in 1.8.7. I check to see if the class in question is being double loaded:
...
class Header < BinData::Record
puts "I'm being loaded!"

hide :u1
...
But I only see the one load:
➜  spdy git:(master) rspec ./spec/protocol_spec.rb -cfs
I'm being loaded!
/home/cstrom/repos/spdy/lib/spdy/protocol.rb:45: field 'type' shadows an existing method in SPDY::Protocol::Control::Header (NameError)
from /home/cstrom/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /home/cstrom/.rvm/rubies/ree-1.8.7-2010.02/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /home/cstrom/repos/spdy/lib/spdy.rb:4
...
Hey wait, why is it only failing on the type attribute? Other attributes are defined before it. What's special about type....? Damn.

And then it dawns on me: type is a method on Object in ruby 1.8.

Ugh. If it were up to me, I would stick with calling the field type because that stays closer to the SPDY draft specification. Besides, Object#type is deprecated in 1.8.7. It may end up being too difficult to maintain, but this compatibility library mostly gets the specs passing without altering the 1.9 goodness:
if Object.instance_methods.include? "type"
class BinData::DSLMixin::DSLParser
def name_is_reserved_in_1_8?(name)
return false if name == "type"
name_is_reserved_in_1_9?(name)
end
alias_method :name_is_reserved_in_1_9?, :name_is_reserved?
alias_method :name_is_reserved?, :name_is_reserved_in_1_8?

def name_shadows_method_in_1_8?(name)
return false if name == "type"
name_shadows_method_in_1_9?(name)
end
alias_method :name_shadows_method_in_1_9?, :name_shadows_method?
alias_method :name_shadows_method?, :name_shadows_method_in_1_8?
end
end
For both name_is_reserved? and name_shadows_method? in the DSLParser class, I return false for "type" (i.e. the "type" name is not reserved and the "type" method is not being shadowed).

That gets most of the specs passing:
➜  spdy git:(master) ✗ rspec ./spec/protocol_spec.rb -cfs

SPDY::Protocol
NV
should create an NV packet (FAILED - 1)
SYN_STREAM
should create a SYN_STREAM packet
should parse SYN_STREAM packet
SYN_REPLY
should create a SYN_REPLY packet (FAILED - 2)
should parse SYN_REPLY packet
DATA
should create a data frame
should create a FIN data frame
should read a FIN data frame

Failures:

1) SPDY::Protocol NV should create an NV packet
Failure/Error: nv.to_binary_s.should == NV
expected: "\000\003\000\fContent-Type\000\ntext/plain\000\006status\000\006200 OK\000\aversion\000\bHTTP/1.1"
got: "\000\003\000\fContent-Type\000\ntext/plain\000\aversion\000\bHTTP/1.1\000\006status\000\006200 OK" (using ==)
Diff:
@@ -1,3 +1,3 @@
Content-Type
-text/plainstatus200 OKversioHTTP/1.1
+text/plainversioHTTP/1.1status200 OK
# ./spec/protocol_spec.rb:10

2) SPDY::Protocol SYN_REPLY should create a SYN_REPLY packet
Failure/Error: sr.to_binary_s.should == SYN_REPLY
expected: "\200\002\000\002\000\000\0005\000\000\000\001\000\000x\273\337\242Q\262b`f\340q\206\006R\b0\220\030\270\020v0\260A\2243\260\001\223\261\202\2777\003;T#\003\a\314<\000\000\000\000\377\377"
got: "\200\002\000\002\000\000\0005\000\000\000\001\000\000x\273\337\242Q\262b`f\340q\206\006R\b0\220\030\270\020v0\260C\0252p\300\3643\260AL``\003\246l\005\177o\000\000\000\000\377\377" (using ==)
# ./spec/protocol_spec.rb:63

Finished in 0.02327 seconds
The two failures look to be hash ordering related. In 1.9, ruby hashes are ordered, where in 1.8 they are effectively random. I believe the specs need to be adjusted to handle potentially random-ordered hashes (unless Ilya wants to make the gem 1.9-only).

I will pick back up with that tomorrow.

Day #2

No comments:

Post a Comment