We discovered a bug in http-cookie while investigating jruby/jruby#5910.
In AbstractStore, (and also in AbstractSaver I believe) you have code like this:
...
class << self
@@class_map = {}
# Gets an implementation class by the name, optionally trying to
# load "http/cookie_jar/*_store" if not found. If loading fails,
# IndexError is raised.
def implementation(symbol)
@@class_map.fetch(symbol)
rescue IndexError
begin
require 'http/cookie_jar/%s_store' % symbol
@@class_map.fetch(symbol)
rescue LoadError, IndexError
raise IndexError, 'cookie store unavailable: %s' % symbol.inspect
end
end
def inherited(subclass) # :nodoc:
@@class_map[class_to_symbol(subclass)] = subclass
end
def class_to_symbol(klass) # :nodoc:
klass.name[/[^:]+?(?=Store$|$)/].downcase.to_sym
end
end
...
This is not thread-safe in any implementation and is the true cause of our variable object widths.
The problem here is that the inherited hook is called immediately once the HashStore and other subclasses extend AbstractStore. In other words, the class goes into this @@class_map before any of its methods have been defined! As a result, another thread might see and try to instantiate the class before it is fully initialized.
This is the reason we had unpredictable results in jruby/jruby#5910; depending on when the second thread starts inspecting the class, it will see none, some, or all of HashStore's methods defined, giving us a different view of instance variables.
It actually gets worse, though, because it's possible for a second thread to start instantiating HashStore before it is fully defined, which causes AbstractStore's initialize to call the default version of default_options that returns nil, and you will get errors like this:
...
NoMethodError: undefined method `each_pair' for nil:NilClass
initialize at /Users/headius/projects/jruby/lib/ruby/gems/shared/gems/http-cookie-1.0.3/lib/http/cookie_jar/abstract_store.rb:52
repro.rb at repro.rb:5
We discovered a bug in http-cookie while investigating jruby/jruby#5910.
In
AbstractStore, (and also inAbstractSaverI believe) you have code like this:This is not thread-safe in any implementation and is the true cause of our variable object widths.
The problem here is that the
inheritedhook is called immediately once theHashStoreand other subclasses extendAbstractStore. In other words, the class goes into this@@class_mapbefore any of its methods have been defined! As a result, another thread might see and try to instantiate the class before it is fully initialized.This is the reason we had unpredictable results in jruby/jruby#5910; depending on when the second thread starts inspecting the class, it will see none, some, or all of
HashStore's methods defined, giving us a different view of instance variables.It actually gets worse, though, because it's possible for a second thread to start instantiating
HashStorebefore it is fully defined, which causesAbstractStore's initialize to call the default version ofdefault_optionsthat returns nil, and you will get errors like this: