3
\$\begingroup\$

Inspired by the non-locking implementation proposed in this post, the following code is an attempt to (about) do the same using the azure cache. As I'm totally green on the Azure cache I'd appreciate input on best practices or pitfalls. I should add that this method is assumed to be the only interaction with the azure cache in the application.

public class AzureCacheWrapper
{
    private readonly DataCache cache;

    public AzureCacheWrapper(DataCacheFactory cacheFactory)
    {
        this.cache = cacheFactory.GetDefaultCache();
    }

    public T GetOrAdd<T>(string key, Func<T> factoryMethod)
    {
        var factoryMethodAsLazy = new Lazy<T>(factoryMethod);
        var cachedFactory = cache.Get(key) as Lazy<T>;
        if (cachedFactory == null)
        {
            try
            {
                cache.Add(key, factoryMethodAsLazy);
                cachedFactory = factoryMethodAsLazy;
            }
            catch (DataCacheException ex)
            {
                if (ex.ErrorCode != DataCacheErrorCode.KeyAlreadyExists)
                {
                    throw;
                }

                // We know for sure that the key exists at this point:
                // Two concurrent callers tried to get it, and this thread
                // happened to be deadlock victim.
                cachedFactory = (Lazy<T>)cache.Get(key);
            }
        }

        return cachedFactory.Value;
    }
}
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

I do not have an Azure account, so I cannot test this, but does this work in general? The linked question uses a MemoryCache which is in-memory and probably references the added objects directly. As Azure cache is a distributed system, that will probably not work and the values need to be serialized in some way to be passed to the remote cache.

As I was writing this, I stumbled upon this SO question which clearly shows that Lazy<T> is serializable and thus can probably be stored in Azure, but the value will be evaluated on serialization. Basically, using Lazy<T> does not give you any advantage here, because of the aforementioned side-effect of putting it in the cache.

            // We know for sure that the key exists at this point:
            // Two concurrent callers tried to get it, and this thread
            // happened to be deadlock victim.

That state is probably not true, because the cache item with that key might have expired between the exception and the call to Get. Granted, that this is relatively unlikely and heavily depends on how small the cache timeout is, but it could happen. This scenario could be avoided if you retried the whole method upon receiving a DataCacheErrorCode.KeyAlreadyExists error code.

\$\endgroup\$
1
  • \$\begingroup\$ Add() seems (deducted from source) to serialize before checking if key exists, which does indeed render Lazy unneccessary, a recalculation seems inevitable at this point \$\endgroup\$
    – Tewr
    Commented Aug 11, 2014 at 15:32
2
\$\begingroup\$
// Two concurrent callers tried to get it, and this thread
// happened to be deadlock victim.

This is inaccurate. The situation that can happen here is a race condition, but not a deadlock (i.e. two processes waiting on each other, which some systems solve by killing one of the processes).

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Imo these type of answers should be considered as comments \$\endgroup\$
    – user
    Commented Aug 11, 2014 at 16:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.