Note: The code summary shown below is not a distillation of the code that I had the problem with. I've left this original summary here since someone else already answered, but the actual code is shown in the answer I've provided below.
I haven't been able to isolate this to a small failing test case, but I'm getting a failure with the following general construct:
class Foo
@mutex = Mutex.new
....
def self.bar
@mutex.synchronize { ... }
end
end
If I create multiple threads invoking Foo.bar
, sometimes @mutex
will evaluate to nil
in bar
. If I use a constant (e.g. MUTEX) instead of an instance variable, I don't have this problem.
I don't know if it's significant, but I'm running on JRuby on a multi-core machine.
I'd appreciate any explanation or help in how to isolate the problem.
Update: I believe this is related to autoloading. With Rails, I was able to reproduce a similar problem with the following contents of foo.rb
in one of the directories Rails autoloads from:
class Foo
@mutex = Mutex.new
def self.bar
@mutex.synchronize {}
end
end
When I then execute the following in the Rails console:
1.upto(4).map { Thread.new { Foo.bar }}.map(&:join)
I get the following error:
RuntimeError: Circular dependency detected while autoloading constant Foo
from /Users/palfvin/.rvm/gems/jruby-1.7.10@javlats/gems/activesupport-4.0.1/lib/active_support/dependencies.rb:461:in `load_missing_constant'
from /Users/palfvin/.rvm/gems/jruby-1.7.10@javlats/gems/activesupport-4.0.1/lib/active_support/dependencies.rb:184:in `const_missing'
from (irb):1:in `evaluate'
and this behavior is the same in CRuby (MRI Ruby).
While autoloading is indeed not thread safe in Rails like it is in Ruby 1.9 (per Is autoload thread-safe in Ruby 1.9?), the problem I encountered was not due to that problem and the code I had was not an instance of the code I showed above, but rather an instance of the following:
The problem is that when executing method from a superclass, the value of
self
remains unchanged, so the value of@mutex
withinFoo.bar
is interpreted in the context of theFoobar
object, not the value of theFoo
object.This problem can be avoided by using a class variable (e.g.
@@mutex
) for the mutex.Does it happen with a class variable?
@@mutex
. There might be a race condition with making new class-instances between threads and the new copy of@mutex
isn't ready yet. Constants and class variables however, are shared between copies of the class and subclasses. Also, what if you put the@mutex
init code in a memoized method such as: