dotnet/dotNext

ConcurrentCache throws NullReferenceException

shaltielshmid opened this issue ยท 10 comments

We are using the ConcurrentCache class from DotNext.Runtime.Caching in our project, with the LFU eviction policy.

We access values using the TryGetValue function, and assign values using the indexing operator.

// Initialize the cache
int capacity = 10000;
var cache = new ConcurrentCache<string, string>(capacity, CacheEvictionPolicy.LFU);

// Get value
bool success = cache.TryGetValue(key, out var value);
// Set value
cache[key] = newValue;

We used this in our project and ran it with multiple threads running simultaneously and after some time it crashed with a NullReferenceException. There is no specific instance that it crashes on, every time we run it it crashes at a different point.

Below is the full error:

Object reference not set to an instance of an object.
 at DotNext.Runtime.Caching.ConcurrentCache`2.DrainQueue()
 at DotNext.Runtime.Caching.ConcurrentCache`2.EnqueueAndDrain(Func`2 invoker, KeyValuePair target)
 at DotNext.Runtime.Caching.ConcurrentCache`2.TryGetValue(TKey key, IEqualityComparer`1 keyComparer, Int32 hashCode, TValue& value)

Any advice would be much appreciated!

Thank you

sakno commented

Could you turn on debugging symbols or launch the app in Debug mode? This will able to see line numbers within the code.

Here are the full details of the exception:

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=DotNext
  StackTrace:
   at DotNext.Runtime.Caching.ConcurrentCache`2.DrainQueue() in /_/src/DotNext/Runtime/Caching/ConcurrentCache.Queue.cs:line 125

In addition, here is minimal code that causes the exception:

          var ds = new Bogus.DataSets.PhoneNumbers();
          var cache = new ConcurrentCache<string, string>(10000, CacheEvictionPolicy.LFU);

          Enumerable.Range(0, 14).AsParallel().ForAll(_ => {
              foreach (int i in Enumerable.Range(0, 100000)) {
                  string num = ds.PhoneNumber();
                  if (cache.TryGetValue(num, out var _))
                      continue;
                  cache[num] = num;
              }
          });
sakno commented

Good to have some code snippet, but I still can't use it because Bogus.DataSets.PhoneNumbers is unknown to me. Could you fix it in the way when I can just copy-paste the code and get the error?

Sure, here you go:

            var cache = new ConcurrentCache<string, string>(10000, CacheEvictionPolicy.LFU);

            Enumerable.Range(0, 14).AsParallel().ForAll(_ => {
                foreach (int i in Enumerable.Range(0, 100000)) {
                    string num = Guid.NewGuid().ToString();
                    if (cache.TryGetValue(num, out var _))
                        continue;
                    cache[num] = num;
                }
            });
sakno commented

Preliminary analysis: the issue with internal pooling. Replacing RentCommand with a trivial implementation that instantiates a fresh instance every time demonstrates stable execution w/o exception.

sakno commented

Related discussion on StackOverflow.

sakno commented

@shaltielshmid , could you check the latest patch from develop branch?

Absolutely, will test it by us tonight.

Works perfectly! Thank you!

sakno commented

A new version has been published.