Search code examples
c#-4.0cachingthread-safetylockingmemorycache

Preventing dirty reads when refreshing MemoryCache via CacheEntryRemovedCallback


I have implemented a MemoryCache that "auto-refreshes" the content via a CacheEntryRemovedCallback.

However, I am having problems preventing dirty reads when the item is removed from the cache.

My cache is being literally pounded by requests, and I need to make sure that the cache entries are being prevented from reading when they are being updated.

My code is below. As you can see I have tried to use a simple lock() on the update of the cache as well as when data is read. But nevertheless reads are done in the time from the item is removed and the lock is obtained.

public List<Domain.Turbine> IgnorableTurbines
{
    get
    {
        var ignorableTurbines = new List<Domain.Turbine>();
        lock (ignorableTurbineslockObject)
        {
            Log.Info("IgnorableTurbines get lock");
            Log.Info("Getting ignorable turbines");
            ignorableTurbines = (List<Domain.Turbine>)Cache["ignorableturbines"];

        }
        Log.Info("IgnorableTurbines get released");
        return ignorableTurbines;
    }
}

void IgnorableStations_CacheItemRemovedCallback(CacheEntryRemovedArguments arguments)
{

    lock (ignorableTurbineslockObject)
    {
        Log.Info("IgnorableTurbines update locked");
        Log.InfoFormat("Item removed: {1}", arguments.CacheItem.Key);
        var decoupledIgnorableTurbines = _repo.Query<ExcludedStation>().ToList();

        AddIgnorableStations("ignorableturbines", decoupledIgnorableTurbines);
        Log.Info("IgnorableTurbines re-added");
    }
    Log.Info("IgnorableTurbines update released");
}

My logging says this:

2014-05-20 15:20:45,855 INFO - IgnorableTurbines get lock
2014-05-20 15:20:45,855 INFO - Getting ignorable turbines
2014-05-20 15:20:45,857 INFO - IgnorableTurbines update locked
2014-05-20 15:20:45,863 INFO - Item removed: ignorableturbines
2014-05-20 15:20:45,866 INFO - IgnorableTurbines re-added
2014-05-20 15:20:45,867 INFO - IgnorableTurbines update released
2014-05-20 15:20:45,868 INFO - IgnorableTurbines get released
2014-05-20 15:20:45,871 ERROR - Value cannot be null.
Parameter name: source
System.ArgumentNullException: Value cannot be null.
Parameter name: source
   at System.Linq.Queryable.AsQueryable[TElement](IEnumerable`1 source)
   at (...).get_IgnorableTurbines()

What puzzles me is that a lock on "get" is obtained and before it is released a lock on "update" is also obtained.

(I am using log4net for logging)

What am I doing wrong?


Solution

  • Yesterday I was wondering if my problem was caused by the event being fired after the cache item was removed, which meant that the lock I was trying to obtain would actually be locking "nothing" - thus causing the "value cannot be null".

    So I digged arround a bit and found another event to subscribe to in the Cache, and it turns out that the CacheItem object has a property called "UpdateCallback".

    So I changed my code to use a CachEntryUpdateCallback instead, and now everything works.

    The code is now for the update-event:

    void IgnorableStations_CacheItemUpdateCallback(CacheEntryUpdateArguments arguments)
    {
        Log.Info("Refreshing ignorable stations");
        var decoupledIgnorableTurbines =
        _repo.Query<ExcludedStation>().Select(i => new Domain.Turbine { StationId = i.WtgNumber, WindfarmId = i.WindFarmId }).ToList();
    
        arguments.UpdatedCacheItem = new CacheItem(arguments.Key, decoupledIgnorableTurbines);
        CacheEntryUpdateCallback ignorableStationsCacheItemBeforeUpdateCallback = IgnorableStations_CacheItemUpdateCallback;
        arguments.UpdatedCacheItemPolicy = new CacheItemPolicy
        {
            AbsoluteExpiration = DateTime.Now.AddMinutes(7.00),
            UpdateCallback = ignorableStationsCacheItemBeforeUpdateCallback
        };
    }
    

    And the initial insertion:

        void AddIgnorableStations(string key, object ignorableStations)
        {
            CacheEntryUpdateCallback ignorableStationsCacheItemBeforeUpdateCallback = IgnorableStations_CacheItemUpdateCallback;
            var cacheItemPolicy = new CacheItemPolicy
            {
                AbsoluteExpiration = DateTime.Now.AddMinutes(7.00),
                UpdateCallback = ignorableStationsCacheItemBeforeUpdateCallback
            };
    
            Cache.Set(key, ignorableStations, cacheItemPolicy);
        }