Tuesday, December 30, 2008

A closer look at ruby gems

Have you ever been stuck at "Bulk updating Gem source index for http://gems.rubyforge.org/" while installing a gem?!Due the to the latest drop in the Internet connectivity in the middle east and my own country, Egypt, installing gems started to be like hell.. I had to wait for hours before having my library installed so I decided to have a closer look. surprisingly.. it was due to a bug in rubygems 1.0.1.

You can easily overcome this by updating to the latest gem 1.3.1 through gem update --system

Gem downloads libraries metadata about all available gems in a gem server. by default we have gem source "http://gems.rubyforge.org/" and recently i added "http://gems.github.com".gem saves this data in a cache file before it performs the required operation, so it doesn't have to download it again the next time.

Part of the code that reads the cache looks like this:


MARSHAL_FIELDS = { -1 => 16, 1 => 16, 2 => 16 }
#...
# Load custom marshal format, re-initializing defaults as needed
def self._load(str)
   array = Marshal.load str

   spec = Gem::Specification.new
   spec.instance_variable_set :@specification_version, array[1]

   current_version = CURRENT_SPECIFICATION_VERSION

   field_count = MARSHAL_FIELDS[spec.specification_version]

   if field_count.nil? or array.size < field_count then
      raise TypeError, "invalid Gem::Specification format #{array.inspect}"
   end
...
end

That above load method is executed to load and validate the metadata of each library from the cache unless an exception is thrown. field_count represents the expected number of attributes per gem according the that gem specification version (either -1, 1, 2 but it is 16 in all cases). the keys of the MARSHAL_FIELDS hash are numbers. but the spec.specification_version is string representation of that number. so MARSHAL_FIELDS[spec.specification_version] is always nil.

Changing it to MARSHAL_FIELDS[spec.specification_version.to_i] made the rubygems work like any civilized tool :).