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

Dependency memoization in consumer classes is not thread-safe #39

Open
KevinBrowne opened this issue Apr 10, 2024 · 0 comments
Open

Dependency memoization in consumer classes is not thread-safe #39

KevinBrowne opened this issue Apr 10, 2024 · 0 comments
Labels

Comments

@KevinBrowne
Copy link
Member

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.size

Expected:

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.

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

No branches or pull requests

1 participant