Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thread-unsafe class caching #27

Open
headius opened this issue Oct 18, 2019 · 3 comments · May be fixed by #43
Open

Thread-unsafe class caching #27

headius opened this issue Oct 18, 2019 · 3 comments · May be fixed by #43

Comments

@headius
Copy link

headius commented Oct 18, 2019

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
@brasic
Copy link

brasic commented Jun 28, 2023

@knu I see that you are now the maintainer of this project. This is still an issue with JRuby and other ruby implementations that do not include a GIL all ruby implementations, requiring workarounds to avoid the lazy class loading. Would you be open to a pull request that fixes it by eagerly loading all CookieJar implementations at require time instead of lazily loading in a thread-unsafe way?

edit: I've opened a PR to fix this at #43.

brasic added a commit to brasic/http-cookie that referenced this issue Jun 28, 2023
Dynamically requiring implementations at runtime in this way is not safe
in a multithreaded program, even in MRI with the GIL. We can simplify
this by just using autoload and turning the @@class_map into a simple
case statement.

Fixes sparklemotion#27
brasic added a commit to brasic/http-cookie that referenced this issue Jun 28, 2023
Dynamically requiring implementations at runtime in this way is not safe
in a multithreaded program, even in MRI with the GIL. We can simplify
this by just using autoload and turning the @@class_map into a simple
case statement.

Fixes sparklemotion#27
@brasic brasic linked a pull request Jun 28, 2023 that will close this issue
@polyakovigor
Copy link

Hello @knu
Can you please provide the status for the issue highlighted here? I'm experiencing the same error.
Thanks

@headius
Copy link
Author

headius commented Oct 9, 2023

The PR looks fine to me but I am not an expert on this library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants