-
-
Notifications
You must be signed in to change notification settings - Fork 70
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 "autoload" code can cause constants to be missing #121
Comments
Thanks, @headius. Your description of a possible sequence that triggers this is very helpful.
I'm all ears if you've got a suggestion that satisfies the user experience constraints we're operating under. We're specifically dealing with two constraints that are practically polar opposites:
Up to now we've satisfied these two constraints with a two-pronged approach:
@headius, do you have a suggestion for a different way we could manage these constants while still satisfying these constraints? I will say that I don't believe this is the source of the Given that no RSpec user has ever reported a problem with this, if we can't come up with an alternate solution, my instinct is to leave it in place in spite of the fact that it's not threadsafe. Although, we may want to document it as unsupported (e.g. "Accessing RSpec's constants from multiple threads during the loading/setup phase is not supported.") |
Yes, I agree. And this is even less likely given that this is a test framework, and people probably don't access RSpec::Matchers in production code. If you wanted to make this safe, you would want to use a pattern that lazily defines the Matchers constant so that once it is visible, it is also ready to be used. That pattern would look something like this: module RSpec
matchers = Module.new do
...body of Matchers
end
Matchers = matchers
end This is obviously not typical Ruby code but your const_missing autoload is pretty atypical too. Unfortunately this is a fundamental problem with how Ruby assigns the constant for an in-progress class definition. I have proposed that the class's name constant not be assigned until the class has closed, but people generally oppose the idea. I also agree this may not be something that needs to be fixed, but it came up as a possible bug reproduction, crossed my desk, and I found this. |
Clever :). With that in case, would we also need a mutex to guard the logic in our
Yeah, I'm realizing that it could get messy since we re-open the For now I think it's unlikely we'll do anything but I'll keep thinking about it. |
What about using |
The issue I was thinking of was this: # lib/rspec/matchers.rb
module RSpec
matchers = Module.new do
# the existing matchers code would go here
end
require 'rspec/matchers/dsl'
Matchers = matchers
end # in lib/rspec/matchers/dsl.rb
# we want to do this...but we don't have a reference to the matchers module!
anonymous_matchers_module.module_exec do
module DSL
end
end The problem here is that we want to wait to assign the matchers module to a constant until the definition is complete. However, in other files we define additional things in the matchers module, but we don't have a reference to the matchers module to re-open it since the matchers module is only available via a local variable accessible in the other file at that point. We can, of course do something messy like store the anonymous matchers module in a global variable or thread local or something else...but that's really messy. The other issue is that constant definition works differently in ➜ rspec-core git:(example-creation-refactoring) irb
irb(main):001:0> Foo = Module.new
=> Foo
irb(main):002:0> Foo.module_exec do
irb(main):003:1* Bar = 1
irb(main):004:1> end
=> 1
irb(main):005:0> Foo::Bar
NameError: uninitialized constant Foo::Bar
from (irb):5
from :0
irb(main):006:0> Bar
=> 1 We reopen the Neither of these issues is insurmountable, of course, but they would probably take fairly significant restructurings, and I'm not convinced the effort would be worth it given the unlikeliness of users hitting this issue in a test suite (as discussed above). |
Auditing old issues that mention me... This code still exists but could now be replaced with |
This may or may not be serious, depending on how thread-safe you consider RSpec itself to be, but it did lead to some red herring bugs in JRuby and Rubinius (jruby/jruby#2874 and rubinius/rubinius#2934).
This code to "autoload" a few namespaces under RSpec is not thread-safe:
This logic is essentially the same problem as using defined?(SomeConst) to determine whether a library has been loaded. Here's a possible sequence leading to the error above.
Synchronizing const_missing does not help, because once the Matchers module is defined we won't even call it. Autoload is not at fault here obviously, but neither is require logic, since no amount of locking can prevent this code from possibly seeing a partially-initialized Matchers module. RSpec needs to change how it manages these constants.
And finally, a detail I should have checked immediately...this happens in MRI too:
The text was updated successfully, but these errors were encountered: