-
Notifications
You must be signed in to change notification settings - Fork 638
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
Verify ConcurrentDictionary use, taking into account GetOrAdd can call creation callback more than once #417
Comments
David Fowlers statement makes it sound like the implementation would call "the" callback (singular) multiple times, I don't feel like that is quite an accurate way of describing the behavior. In the actual behavior, it makes a bit more sense why this happens. The basics is that the factory is called outside of a lock, which means if you call GetOrAdd simultaneously on two threads, each or their value factories will be executed. This may be a good read: https://andrewlock.net/making-getoradd-on-concurrentdictionary-thread-safe-using-lazy/ Usages: ConcurrentDictionary<TKey, TValue>Reference source:
GetOrAddLucene.Net: https://github.com/apache/lucenenet/blob/master/src/Lucene.Net/Support/Configuration/EnvironmentVariablesConfigurationProvider.cs#L67 https://github.com/apache/lucenenet/blob/master/src/Lucene.Net/Search/SearcherLifetimeManager.cs#L174 Lucene.Net.TestFramework: lucenenet/src/Lucene.Net.TestFramework/Support/Configuration/TestConfigurationFactory.cs Line 56 in f5cca0b
-> Could be candidate for using Lazy pattern unless we say that this is "TestFramework" and therefore unlikely to cause a problem. Also I did not dive deep into what the builder does and how expensive it is. The fix is not that problematic in the end though. Lucene.Net.Spatial:
-> Assuming inexpensive call to constructor. AddOrUpdateCould not find any usages. ConditionalWeakTable<TKey, TValue>Reference source: GetValueAddOrUpdateGetOrCreateValueLurchTable<TKey, TValue>GetOrAddAddOrUpdate |
Thanks for the insight.
However, ConditionalWeakTable<TKey, TValue>ConcurrentDictionary<TKey, TValue>LurchTable<TKey, TValue>In addition, we should expand the search to include all of Lucene.NET's dependencies that we maintain. Although the documentation doesn't specify that locking is an issue with Just doing a quick survey, I noticed there are some calls that should have locks in J2N and it is suspected that this may be the source of concurrency issues with |
@jeme Great research. Thanks for pointing out Andrew Locks' article which explains the situation well. It was a good read. |
… issues with adding new items to the cache and reading RamBytesUsed() method (see apache#417)
…e with loading the cache by using Lazy<T>. Fixes apache#319. Also see apache#417.
…ing it doesn't matter if multiple threads compete to populate the ConditionalWeakTable. See apache#417.
…ovider): Added comments to indicate that multiple threads competing in the valueFactory is okay in these cases. See apache#417.
…y: Use Lazy<T> to ensure the configurationCache.GetOrAdd() factory is atomic. See apache#417.
…tDictionary to make the valueFactory atomic. See apache#417. Fixes apache#319.
… by using GetOrAdd() instead of TryGetValue. See apache#417.
… the CleanupTemporaryFiles() method to be more in line with the original Java implementation, including not allowing new files/directories to be added to the queue concurrently with the deletion process. See apache#417.
…ag<T> with ConcurrentQueue<T> because we need to be sure the underlying implementation guarantees order and the extra call to Reverse() was just slowing things down. See apache#417.
…the reason we use Lazy<T> is to make the create operation atomic. See apache#417.
… the CleanupTemporaryFiles() method to be more in line with the original Java implementation, including not allowing new files/directories to be added to the queue concurrently with the deletion process. See apache#417.
…ag<T> with ConcurrentQueue<T> because we need to be sure the underlying implementation guarantees order and the extra call to Reverse() was just slowing things down. See apache#417.
…the reason we use Lazy<T> is to make the create operation atomic. See apache#417.
… issues with adding new items to the cache and reading RamBytesUsed() method (see #417)
…ing it doesn't matter if multiple threads compete to populate the ConditionalWeakTable. See #417.
…ovider): Added comments to indicate that multiple threads competing in the valueFactory is okay in these cases. See #417.
…y: Use Lazy<T> to ensure the configurationCache.GetOrAdd() factory is atomic. See #417.
… by using GetOrAdd() instead of TryGetValue. See #417.
… the CleanupTemporaryFiles() method to be more in line with the original Java implementation, including not allowing new files/directories to be added to the queue concurrently with the deletion process. See #417.
…ag<T> with ConcurrentQueue<T> because we need to be sure the underlying implementation guarantees order and the extra call to Reverse() was just slowing things down. See #417.
…the reason we use Lazy<T> is to make the create operation atomic. See #417.
Recently I saw that David Fowler, lead architect on the .Net Core team at Microsoft, commented that one of the common issues that he has observed is that when people use
ConcurrentDictionary
they don't take into account that theGetOrAdd
method executes the creation callback more than once.And this can be a problem if the code is creating something expensive for example.
Apparently this is documented but certainly not the behavior that one would expect. We should review the use of
ConcurrentDictionary
GetOrAdd
calls to ensure we are accounting for this odd behavior.The text was updated successfully, but these errors were encountered: