MichaCo / CacheManager

CacheManager is an open source caching abstraction layer for .NET written in C#. It supports various cache providers and implements many advanced features.
http://cachemanager.michaco.net
Apache License 2.0
2.35k stars 456 forks source link

DictionaryHandle - sporadic NullReferenceException #167

Closed burnstek closed 7 years ago

burnstek commented 7 years ago

Hello! Rarely, we see this occur. I don't know the steps to repro, but it does happen from time to time. We can't repro with MemoryCacheHandle.

06/14 15:42:40 - System.NullReferenceException: Object reference not set to an instance of an object.
   at CacheManager.Core.Internal.DictionaryCacheHandle`1.GetCacheItemInternal(String key, String region)
   at CacheManager.Core.Internal.DictionaryCacheHandle`1.GetCacheItemInternal(String key)
   at CacheManager.Core.Internal.BaseCache`1.GetCacheItem(String key)
   at CacheManager.Core.BaseCacheManager`1.GetCacheItemInternal(String key, String region)
   at CacheManager.Core.BaseCacheManager`1.GetCacheItemInternal(String key)
   at CacheManager.Core.Internal.BaseCache`1.GetCacheItem(String key)
   at CacheManager.Core.Internal.BaseCache`1.Get(String key)
   at CacheManager.Core.Internal.BaseCache`1.Get[TOut](String key)
   at DataCache.DataCacheClient.Get[T]() in 

Looking at DictionaryCacheHandle.cs, I have no idea what could be going wrong. Apparently _cache is null on this line, but it's not clear at all how that could be.

if (_cache.TryGetValue(fullKey, out CacheItem<TCacheValue> result))

Here is a sample config I use to reproduce this:

_cache = CacheFactory.Build(s => s
                .WithJsonSerializer()
                .WithDictionaryHandle()
                .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromSeconds(60)) 
                .And
                .WithRedisConfiguration("redis", multiplexer, 1)
                .WithRedisCacheHandle("redis")
                .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromSeconds(180))); 

And the test harness:

                        for (int j = 0; j < 100; j++) // test this N times.
                        {
                            new Thread(() =>
                            {
                                _cache.Get("some_key");
                            }).Start();
                        }
MichaCo commented 7 years ago

I cannot reproduce the error at all. Running it on Windows 10 Fullframework and netstandard not on Linux.

MichaCo commented 7 years ago

Ok turns out I already fixed that part in a more recent check-in...

There was a potential race condition issue in line 127 where result can be null if the TryRemove returns false. That was a bug.

Will be fixed in the next patch release... sorry ;)

here is a more reliable way to reproduce it

   var _cache = CacheFactory.Build(s => s
        .WithDictionaryHandle()
        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMilliseconds(10))
        .And
        .WithDictionaryHandle()
        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMilliseconds(20))
        .And
        .WithDictionaryHandle()
        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMilliseconds(30))
        .And
        .WithDictionaryHandle()
        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMilliseconds(40))
        .And
        .WithDictionaryHandle()
        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMilliseconds(50))
        );

    _cache.OnRemoveByHandle += (s, a) =>
    {
        Console.Write(a.Level);
    };

    while (true)
    {
        Action act = () =>
        {
            try
            {
                var val = _cache.Get("some_key");
                if (val == null)
                {
                    _cache.Put("some_key", "value");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        };

        Parallel.Invoke(Enumerable.Repeat(act, 1000).ToArray());
    }
MichaCo commented 7 years ago

fixed in 1.1.1