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

Kind fetcher locks are not fully thread-safe #911

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

mensfeld
Copy link

In case of extensive concurrent usage, the mutex handed over to two threads under same key may differ as illustrated below:

require 'concurrent'

10000.times do
  kind_fetcher_locks = Concurrent::Hash.new { |hash, key| hash[key] = Mutex.new }

  refs = Set.new
  100.times.map do |i|
    Thread.new { refs << kind_fetcher_locks[i % 50].object_id }
  end.each(&:join)

  raise "Not 50 but #{refs.count}" unless refs.size == 50
end

this can lead to really weird issues.

Works when fixed as above:

require 'concurrent'

10000.times do
  mutex = Mutex.new
  # kind_fetcher_locks = Concurrent::Hash.new { |hash, key| hash[key] = Mutex.new }

  kind_fetcher_locks = Concurrent::Hash.new do |hash, key|
    mutex.synchronize do
      break hash[key] if hash.key?(key)
      hash[key] = Mutex.new
    end
  end

  refs = Set.new
  100.times.map do |i|
    Thread.new { refs << kind_fetcher_locks[i % 50].object_id }
  end.each(&:join)

  raise "Not 50 but #{refs.count}" unless refs.size == 50
end

ref ruby-concurrency/concurrent-ruby#970

What are you trying to accomplish with this PR?
...

How is this accomplished?
...

What could go wrong?
...

In case of extensive concurrent usage, the mutex handed over to two threads under same key may differ as illustrated below:

```ruby
require 'concurrent'

10000.times do
  kind_fetcher_locks = Concurrent::Hash.new { |hash, key| hash[key] = Mutex.new }

  refs = Set.new
  100.times.map do |i|
    Thread.new { refs << kind_fetcher_locks[i % 50].object_id }
  end.each(&:join)

  raise "Not 50 but #{refs.count}" unless refs.size == 50
end
```

this can lead to really weird issues.

Works when fixed as above:

```ruby
require 'concurrent'

10000.times do
  mutex = Mutex.new
  # kind_fetcher_locks = Concurrent::Hash.new { |hash, key| hash[key] = Mutex.new }

  kind_fetcher_locks = Concurrent::Hash.new do |hash, key|
    mutex.synchronize do
      break hash[key] if hash.key?(key)
      hash[key] = Mutex.new
    end
  end

  refs = Set.new
  100.times.map do |i|
    Thread.new { refs << kind_fetcher_locks[i % 50].object_id }
  end.each(&:join)

  raise "Not 50 but #{refs.count}" unless refs.size == 50
end
```

ref ruby-concurrency/concurrent-ruby#970
@mensfeld mensfeld requested a review from a team as a code owner November 21, 2022 11:01
@mensfeld mensfeld requested review from ethanaubuchon and aajoslinify and removed request for a team November 21, 2022 11:01
@mensfeld
Copy link
Author

mensfeld commented Nov 21, 2022

I don't have an option to trigger a re-run of CI, signed the CLA

// o nice, picked up my comment

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 this pull request may close these issues.

1 participant