-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Class.dependency, which is monkey-patched in by the gem, creates an instance method for the dependency. This instance method does a check-and-set of an instance variable for the resolved dependency to ensure it queries the container for the instance only once.
Basically this:
class Foo
dependency :bar
# etc.creates this
class Foo
def bar
val = instance_variable_get(:@bar)
if val.nil?
val = Sinject::Container.instance.get(:bar)
instance_variable_set(:@bar, val)
end
val
end
# etc.This check-and-set memoization is not thread-safe.
A contrived example:
require 'sinject'
class Foo
dependency :bar
end
foo = Foo.new
container = Sinject::Container.new(false)
container.register(key: :bar, class: Array, singleton: false) { sleep(1); [] }
objects = Set.new.compare_by_identity
mutex = Mutex.new
3500.times.map do
Thread.new do
o = foo.bar
mutex.synchronize { objects.add(o) }
end
end.each(&:join)
objects.sizeExpected:
Even though the dependency is not a singleton, foo.bar should always return the same instance. Therefore, objects.size should be 1.
Actual:
objects.size is greater than 1, the exact number being subject to the timing of the threads' execution.
Where objects.size is less than the number of threads spawned, this shows that eventually #bar does memoize the result and return the same instance on subsequent calls.