예제 #1
0
 /// <summary>
 /// Initializes a new instance of the <see cref="CacheUpdateEventArgs{TCacheValue}" /> class.
 /// </summary>
 /// <param name="key">The key.</param>
 /// <param name="region">The region.</param>
 /// <param name="config">The configuration.</param>
 /// <param name="result">The result.</param>
 public CacheUpdateEventArgs(string key, string region, UpdateItemConfig config, UpdateItemResult <TCacheValue> result)
 {
     this.Key    = key;
     this.Region = region;
     this.Result = result;
     this.Config = config;
 }
예제 #2
0
        public void CacheManager_Update_Success_ValidateEvict()
        {
            // arrange
            Func <string, string> updateFunc = s => s;

            int updateCalls = 0;
            int addCalls    = 0;
            int putCalls    = 0;
            int removeCalls = 0;

            var cache = MockHandles(
                count: 5,
                updateCalls: Enumerable.Repeat <Action>(() => updateCalls++, 5).ToArray(),
                updateCallResults: new UpdateItemResult <string>[]
            {
                UpdateItemResult.ForItemDidNotExist <string>(),
                UpdateItemResult.ForItemDidNotExist <string>(),
                UpdateItemResult.ForItemDidNotExist <string>(),
                UpdateItemResult.ForSuccess("some value", true, 100),
                UpdateItemResult.ForItemDidNotExist <string>()
            },
                putCalls: Enumerable.Repeat <Action>(() => putCalls++, 5).ToArray(),
                removeCalls: Enumerable.Repeat <Action>(() => removeCalls++, 5).ToArray(),
                getCallValues: new CacheItem <string>[]
            {
                null,
                null,
                null,
                new CacheItem <string>("key", "updated value"),     // have to return an item for the second one
                null
            },
                addCalls: Enumerable.Repeat <Func <bool> >(() => { addCalls++; return(true); }, 5).ToArray());

            cache.Configuration.CacheUpdateMode = CacheUpdateMode.Up;

            // the update config setting it to UpdateOtherCaches
            UpdateItemConfig updateConfig = new UpdateItemConfig(0, VersionConflictHandling.UpdateOtherCaches);

            // act
            using (cache)
            {
                string value;
                var    updateResult = cache.TryUpdate("key", updateFunc, updateConfig, out value);

                // assert
                updateCalls.Should().Be(2, "second from below did update");
                putCalls.Should().Be(0, "no puts");
                addCalls.Should().Be(1, "one below the one updating");
                removeCalls.Should().Be(3, "3 above");
                updateResult.Should().BeTrue("updated successfully.");
            }
        }
예제 #3
0
        public void CacheManager_Update_Validate_LowestWins()
        {
            // arrange
            Func <string, string> updateFunc = s => s;
            int updateCalls = 0;
            int putCalls    = 0;
            int removeCalls = 0;

            var cache = MockHandles(
                count: 5,
                updateCalls: Enumerable.Repeat <Action>(() => updateCalls++, 5).ToArray(),
                updateCallResults: new UpdateItemResult <string>[]
            {
                null,
                null,
                null,
                null,
                UpdateItemResult.ForSuccess <string>(string.Empty, true, 100)
            },
                putCalls: Enumerable.Repeat <Action>(() => putCalls++, 5).ToArray(),
                removeCalls: Enumerable.Repeat <Action>(() => removeCalls++, 5).ToArray());

            cache.Configuration.CacheUpdateMode = CacheUpdateMode.Up;

            // the update config setting it to Ignore: update handling should be ignore, update was success, items shoudl get evicted from others
            UpdateItemConfig updateConfig = new UpdateItemConfig(0, VersionConflictHandling.Ignore);

            // act
            using (cache)
            {
                string value;
                var    updateResult = cache.TryUpdate("key", updateFunc, updateConfig, out value);

                // assert
                updateCalls.Should().Be(1, "first handle should have been invoked");
                putCalls.Should().Be(0, "evicted");
                removeCalls.Should().Be(4, "items should have been removed");
                updateResult.Should().BeTrue();
            }
        }
예제 #4
0
        public void CacheManager_Update_ExceededRetryLimit()
        {
            // arrange
            Func <string, string> updateFunc = s => s;
            int updateCalls = 0;
            int putCalls    = 0;
            int removeCalls = 0;

            var cache = MockHandles(
                count: 5,
                updateCalls: Enumerable.Repeat <Action>(() => updateCalls++, 5).ToArray(),
                updateCallResults: new UpdateItemResult <string>[]
            {
                UpdateItemResult.ForSuccess <string>(string.Empty, true, 100),
                UpdateItemResult.ForSuccess <string>(string.Empty, true, 100),
                UpdateItemResult.ForSuccess <string>(string.Empty, true, 100),
                UpdateItemResult.ForTooManyRetries <string>(1000),
                UpdateItemResult.ForItemDidNotExist <string>(),
            },
                putCalls: Enumerable.Repeat <Action>(() => putCalls++, 5).ToArray(),
                removeCalls: Enumerable.Repeat <Action>(() => removeCalls++, 5).ToArray());

            cache.Configuration.CacheUpdateMode = CacheUpdateMode.Up;

            // the update config setting it to EvictItemFromOtherCaches
            UpdateItemConfig updateConfig = new UpdateItemConfig(0, VersionConflictHandling.EvictItemFromOtherCaches);

            // act
            using (cache)
            {
                string value;
                var    updateResult = cache.TryUpdate("key", updateFunc, updateConfig, out value);

                // assert
                updateCalls.Should().Be(2, "bottom to top");
                putCalls.Should().Be(0, "no put calls expected");
                removeCalls.Should().Be(4, "the key should have been removed from the other 4 handles");
                updateResult.Should().BeFalse("the update in handle 4 was not successful.");
            }
        }
예제 #5
0
        public void CacheManager_Update_ItemDoesNotExist()
        {
            // arrange
            Func <string, string> updateFunc = s => s;
            int updateCalls = 0;
            int putCalls    = 0;
            int removeCalls = 0;

            var cache = MockHandles(
                count: 5,
                updateCalls: Enumerable.Repeat <Action>(() => updateCalls++, 5).ToArray(),
                updateCallResults: new UpdateItemResult <string>[]
            {
                UpdateItemResult.ForItemDidNotExist <string>(),
                UpdateItemResult.ForItemDidNotExist <string>(),
                UpdateItemResult.ForItemDidNotExist <string>(),
                UpdateItemResult.ForItemDidNotExist <string>(),
                UpdateItemResult.ForItemDidNotExist <string>()
            },
                putCalls: Enumerable.Repeat <Action>(() => putCalls++, 5).ToArray(),
                removeCalls: Enumerable.Repeat <Action>(() => removeCalls++, 5).ToArray());

            cache.Configuration.CacheUpdateMode = CacheUpdateMode.Up;

            // the update config setting it to Ignore
            UpdateItemConfig updateConfig = new UpdateItemConfig(0, VersionConflictHandling.EvictItemFromOtherCaches);

            // act
            using (cache)
            {
                string value;
                var    updateResult = cache.TryUpdate("key", updateFunc, updateConfig, out value);

                // assert
                updateCalls.Should().Be(5, "should iterate through all of them");
                putCalls.Should().Be(0, "no put calls expected");
                removeCalls.Should().Be(0);
                updateResult.Should().BeFalse();
            }
        }
예제 #6
0
        /// <summary>
        /// Updates an existing key in the cache.
        /// <para>
        /// The cache manager will make sure the update will always happen on the most recent version.
        /// </para>
        /// <para>
        /// If version conflicts occur, if for example multiple cache clients try to write the same
        /// key, and during the update process, someone else changed the value for the key, the
        /// cache manager will retry the operation.
        /// </para>
        /// <para>
        /// The <paramref name="updateValue"/> function will get invoked on each retry with the most
        /// recent value which is stored in cache.
        /// </para>
        /// </summary>
        /// <param name="key">The key to update.</param>
        /// <param name="region">The cache region.</param>
        /// <param name="updateValue">The function to perform the update.</param>
        /// <param name="config">The cache configuration used to specify the update behavior.</param>
        /// <returns>The update result which is interpreted by the cache manager.</returns>
        /// <remarks>
        /// If the cache does not use a distributed cache system. Update is doing exactly the same
        /// as Get plus Put.
        /// </remarks>
        public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
        {
            var tries   = 0;
            var fullKey = GetKey(key, region);

            return(this.Retry(() =>
            {
                do
                {
                    tries++;

                    var item = this.GetCacheItemInternal(key, region);

                    if (item == null)
                    {
                        return UpdateItemResult.ForItemDidNotExist <TCacheValue>();
                    }

                    var oldValue = this.ToRedisValue(item.Value);

                    // run update
                    var newValue = updateValue(item.Value);

                    var result = this.Eval(ScriptType.Update, new
                    {
                        key = (StackRedis.RedisKey)fullKey,
                        valField = HashFieldValue,
                        val = this.ToRedisValue(newValue),
                        oldVal = oldValue
                    });

                    if (result != null && !result.IsNull)
                    {
                        return UpdateItemResult.ForSuccess <TCacheValue>(newValue, tries > 1, tries);
                    }
                }while (tries <= config.MaxRetries);

                return UpdateItemResult.ForTooManyRetries <TCacheValue>(tries);
            }));
        }
예제 #7
0
 /// <summary>
 /// Updates an existing key in the cache.
 /// <para>
 /// The cache manager will make sure the update will always happen on the most recent version.
 /// </para>
 /// <para>
 /// If version conflicts occur, if for example multiple cache clients try to write the same
 /// key, and during the update process, someone else changed the value for the key, the
 /// cache manager will retry the operation.
 /// </para>
 /// <para>
 /// The <paramref name="updateValue"/> function will get invoked on each retry with the most
 /// recent value which is stored in cache.
 /// </para>
 /// </summary>
 /// <param name="key">The key to update.</param>
 /// <param name="updateValue">The function to perform the update.</param>
 /// <param name="config">The cache configuration used to specify the update behavior.</param>
 /// <returns>The update result which is interpreted by the cache manager.</returns>
 /// <remarks>
 /// If the cache does not use a distributed cache system. Update is doing exactly the same
 /// as Get plus Put.
 /// </remarks>
 public override UpdateItemResult <TCacheValue> Update(string key, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config) =>
 this.Update(key, null, updateValue, config);
예제 #8
0
        private UpdateItemResult <TCacheValue> Set(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
        {
            var fullyKey = GetKey(key, region);
            var tries    = 0;
            IStoreOperationResult result;

            do
            {
                tries++;
                var        getTries = 0;
                StatusCode getStatus;
                CacheItem <TCacheValue> item;
                CasResult <CacheItem <TCacheValue> > cas;
                do
                {
                    getTries++;
                    cas = this.Cache.GetWithCas <CacheItem <TCacheValue> >(fullyKey);

                    item      = cas.Result;
                    getStatus = (StatusCode)cas.StatusCode;
                }while (ShouldRetry(getStatus) && getTries <= config.MaxRetries);

                // break operation if we cannot retrieve the object (maybe it has expired already).
                if (getStatus != StatusCode.Success || item == null)
                {
                    return(UpdateItemResult.ForItemDidNotExist <TCacheValue>());
                }

                item = item.WithValue(updateValue(item.Value));

                if (item.ExpirationMode == ExpirationMode.Absolute)
                {
                    var timeoutDate = item.ExpirationTimeout;
                    result = this.Cache.ExecuteCas(StoreMode.Set, fullyKey, item, timeoutDate, cas.Cas);
                }
                else if (item.ExpirationMode == ExpirationMode.Sliding)
                {
                    result = this.Cache.ExecuteCas(StoreMode.Set, fullyKey, item, item.ExpirationTimeout, cas.Cas);
                }
                else
                {
                    result = this.Cache.ExecuteCas(StoreMode.Set, fullyKey, item, cas.Cas);
                }

                if (result.Success)
                {
                    return(UpdateItemResult.ForSuccess <TCacheValue>(item.Value, tries > 1, tries));
                }
            }while (!result.Success && result.StatusCode.HasValue && result.StatusCode.Value == 2 && tries <= config.MaxRetries);

            return(UpdateItemResult.ForTooManyRetries <TCacheValue>(tries));
        }
예제 #9
0
 /// <summary>
 /// Updates an existing key in the cache.
 /// <para>
 /// The cache manager will make sure the update will always happen on the most recent version.
 /// </para>
 /// <para>
 /// If version conflicts occur, if for example multiple cache clients try to write the same
 /// key, and during the update process, someone else changed the value for the key, the
 /// cache manager will retry the operation.
 /// </para>
 /// <para>
 /// The <paramref name="updateValue"/> function will get invoked on each retry with the most
 /// recent value which is stored in cache.
 /// </para>
 /// </summary>
 /// <param name="key">The key to update.</param>
 /// <param name="region">The cache region.</param>
 /// <param name="updateValue">The function to perform the update.</param>
 /// <param name="config">The cache configuration used to specify the update behavior.</param>
 /// <returns>The update result which is interpreted by the cache manager.</returns>
 /// <remarks>
 /// If the cache does not use a distributed cache system. Update is doing exactly the same
 /// as Get plus Put.
 /// </remarks>
 public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config) =>
 this.Set(key, region, updateValue, config);
 /// <summary>
 /// Updates an existing key in the cache.
 /// <para>
 /// The cache manager will make sure the update will always happen on the most recent version.
 /// </para>
 /// <para>
 /// If version conflicts occur, if for example multiple cache clients try to write the same
 /// key, and during the update process, someone else changed the value for the key, the
 /// cache manager will retry the operation.
 /// </para>
 /// <para>
 /// The <paramref name="updateValue"/> function will get invoked on each retry with the most
 /// recent value which is stored in cache.
 /// </para>
 /// </summary>
 /// <param name="key">The key to update.</param>
 /// <param name="updateValue">The function to perform the update.</param>
 /// <param name="config">The cache configuration used to specify the update behavior.</param>
 /// <returns>The update result which is interpreted by the cache manager.</returns>
 /// <remarks>
 /// If the cache does not use a distributed cache system. Update is doing exactly the same
 /// as Get plus Put.
 /// </remarks>
 public override UpdateItemResult <TCacheValue> Update(string key, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
 {
     return(this.UpdateInternal(key, null, updateValue, config));
 }
        private UpdateItemResult <TCacheValue> UpdateInternal(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
        {
            if (updateValue == null)
            {
                throw new ArgumentNullException("updateValue");
            }

            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            var retries = 0;

            do
            {
                retries++;

                var fullKey = GetKey(key, region);
                var item    = this.GetCacheItemInternal(key, region);
                if (item == null)
                {
                    return(UpdateItemResult.ForItemDidNotExist <TCacheValue>());
                }

                var newValue = updateValue(item.Value);
                var newItem  = item.WithValue(newValue);

                if (this.cache.TryUpdate(fullKey, newItem, item))
                {
                    return(UpdateItemResult.ForSuccess <TCacheValue>(newItem.Value, retries > 1, retries));
                }
            }while (retries <= config.MaxRetries);

            return(UpdateItemResult.ForTooManyRetries <TCacheValue>(retries));
        }
예제 #12
0
 public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
 {
     this.UpdateCall();
     return(this.UpdateValue);
 }
예제 #13
0
        /// <summary>
        /// Updates an existing key in the cache.
        /// <para>
        /// The cache manager will make sure the update will always happen on the most recent version.
        /// </para>
        /// <para>
        /// If version conflicts occur, if for example multiple cache clients try to write the same
        /// key, and during the update process, someone else changed the value for the key, the
        /// cache manager will retry the operation.
        /// </para>
        /// <para>
        /// The <paramref name="updateValue"/> function will get invoked on each retry with the most
        /// recent value which is stored in cache.
        /// </para>
        /// </summary>
        /// <param name="key">The key to update.</param>
        /// <param name="region">The cache region.</param>
        /// <param name="updateValue">The function to perform the update.</param>
        /// <param name="config">The cache configuration used to specify the update behavior.</param>
        /// <returns>The update result which is interpreted by the cache manager.</returns>
        /// <exception cref="System.ArgumentNullException">If updateValue or config are null.</exception>
        /// <remarks>
        /// If the cache does not use a distributed cache system. Update is doing exactly the same
        /// as Get plus Put.
        /// </remarks>
        public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
        {
            if (updateValue == null)
            {
                throw new ArgumentNullException("updateValue");
            }
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            DataCacheLockHandle     lockHandle = null;
            CacheItem <TCacheValue> item       = null;
            var retry = false;  // indicate lock = version conflict
            var hasVersionConflict = false;
            var tries = 0;

            do
            {
                tries++;
                retry = false;
                try
                {
                    if (string.IsNullOrWhiteSpace(region))
                    {
                        item = this.cache.GetAndLock(key, TimeSpan.FromMilliseconds(100), out lockHandle) as CacheItem <TCacheValue>;
                    }
                    else
                    {
                        RegisterRegion(region);
                        item = this.cache.GetAndLock(key, TimeSpan.FromMilliseconds(100), out lockHandle, region) as CacheItem <TCacheValue>;
                    }
                }
                catch (DataCacheException ex)
                {
                    if (ex.ErrorCode == DataCacheErrorCode.ObjectLocked)
                    {
                        // object seems to be locked so we have a version conflict and we'll retry...
                        retry = true;
                        hasVersionConflict = true;
                    }
                    else if (IsTransientError(ex))
                    {
                        retry = true;
                        Task.Delay(DefaultRetryWaitTimeout).Wait();
                    }
                    else
                    {
                        throw;
                    }
                }
            }while (retry && tries <= config.MaxRetries);

            if (item == null)
            {
                return(new UpdateItemResult <TCacheValue>(default(TCacheValue), hasVersionConflict, false, tries));
            }

            do
            {
                // fix: never actually updated the item next retry...
                item = item.WithValue(updateValue(item.Value));

                tries++;
                retry = false;
                try
                {
                    if (string.IsNullOrWhiteSpace(item.Region))
                    {
                        if (item.ExpirationTimeout != TimeSpan.Zero)
                        {
                            this.cache.PutAndUnlock(item.Key, item, lockHandle, item.ExpirationTimeout);
                        }
                        else
                        {
                            this.cache.PutAndUnlock(item.Key, item, lockHandle);
                        }
                    }
                    else
                    {
                        if (item.ExpirationTimeout != TimeSpan.Zero)
                        {
                            this.cache.PutAndUnlock(item.Key, item, lockHandle, item.ExpirationTimeout, item.Region);
                        }
                        else
                        {
                            this.cache.PutAndUnlock(item.Key, item, lockHandle, item.Region);
                        }
                    }

                    return(new UpdateItemResult <TCacheValue>(item.Value, hasVersionConflict, true, tries));
                }
                catch (DataCacheException ex)
                {
                    if (ex.ErrorCode == DataCacheErrorCode.ObjectNotLocked ||
                        ex.ErrorCode == DataCacheErrorCode.InvalidCacheLockHandle ||
                        ex.ErrorCode == DataCacheErrorCode.KeyDoesNotExist)
                    {
                        return(new UpdateItemResult <TCacheValue>(default(TCacheValue), hasVersionConflict, false, tries));
                    }
                    else if (IsTransientError(ex))
                    {
                        Task.Delay(DefaultRetryWaitTimeout).Wait();
                        retry = true;
                    }
                    else
                    {
                        // return new UpdateItemResult(hasVersionConflict, false, tries);
                        throw; // shell we throw the exception? Usually we just return false...
                    }
                }
            }while (retry && tries <= config.MaxRetries);

            return(new UpdateItemResult <TCacheValue>(default(TCacheValue), hasVersionConflict, false, tries));
        }
예제 #14
0
        /// <summary>
        /// Updates an existing key in the cache.
        /// <para>
        /// The cache manager will make sure the update will always happen on the most recent version.
        /// </para>
        /// <para>
        /// If version conflicts occur, if for example multiple cache clients try to write the same
        /// key, and during the update process, someone else changed the value for the key, the
        /// cache manager will retry the operation.
        /// </para>
        /// <para>
        /// The <paramref name="updateValue"/> function will get invoked on each retry with the most
        /// recent value which is stored in cache.
        /// </para>
        /// </summary>
        /// <param name="key">The key to update.</param>
        /// <param name="region">The cache region.</param>
        /// <param name="updateValue">The function to perform the update.</param>
        /// <param name="config">The cache configuration used to specify the update behavior.</param>
        /// <returns>The update result which is interpreted by the cache manager.</returns>
        /// <exception cref="System.ArgumentNullException">
        /// If key, region, updateValue or config are null.
        /// </exception>
        /// <remarks>
        /// If the cache does not use a distributed cache system. Update is doing exactly the same
        /// as Get plus Put.
        /// </remarks>
        public virtual UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
        {
            NotNull(updateValue, nameof(updateValue));

            var original = this.GetCacheItem(key, region);

            if (original == null)
            {
                return(UpdateItemResult.ForItemDidNotExist <TCacheValue>());
            }

            var value = updateValue(original.Value);

            this.Put(key, value, region);
            return(UpdateItemResult.ForSuccess <TCacheValue>(value));
        }
예제 #15
0
        /// <summary>
        /// Updates an existing key in the cache.
        /// <para>
        /// The cache manager will make sure the update will always happen on the most recent version.
        /// </para>
        /// <para>
        /// If version conflicts occur, if for example multiple cache clients try to write the same
        /// key, and during the update process, someone else changed the value for the key, the
        /// cache manager will retry the operation.
        /// </para>
        /// <para>
        /// The <paramref name="updateValue"/> function will get invoked on each retry with the most
        /// recent value which is stored in cache.
        /// </para>
        /// </summary>
        /// <param name="key">The key to update.</param>
        /// <param name="region">The cache region.</param>
        /// <param name="updateValue">The function to perform the update.</param>
        /// <param name="config">The cache configuration used to specify the update behavior.</param>
        /// <returns>The update result which is interpreted by the cache manager.</returns>
        /// <remarks>
        /// If the cache does not use a distributed cache system. Update is doing exactly the same
        /// as Get plus Put.
        /// </remarks>
        public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
        {
            var committed = false;
            var tries     = 0;
            var fullKey   = GetKey(key, region);

            return(this.Retry(() =>
            {
                do
                {
                    tries++;

                    var item = this.GetCacheItemInternal(key, region);

                    if (item == null)
                    {
                        return UpdateItemResult.ForItemDidNotExist <TCacheValue>();
                    }

                    var oldValue = this.ToRedisValue(item.Value);

                    var tran = this.Database.CreateTransaction();
                    tran.AddCondition(StackRedis.Condition.HashEqual(fullKey, HashFieldValue, oldValue));

                    // run update
                    var newValue = updateValue(item.Value);

                    tran.HashSetAsync(fullKey, HashFieldValue, this.ToRedisValue(newValue));

                    committed = tran.Execute();

                    if (committed)
                    {
                        return UpdateItemResult.ForSuccess <TCacheValue>(newValue, tries > 1, tries);
                    }
                    else
                    {
                        //// just for debugging one bug in the redis client
                        //// var checkItem = this.GetCacheItemInternal(key, region);
                        //// if (newValue.Equals(checkItem.Value))
                        //// {
                        ////     throw new InvalidOperationException("Updated although not committed.");
                        //// }
                    }
                }while (committed == false && tries <= config.MaxRetries);

                return UpdateItemResult.ForTooManyRetries <TCacheValue>(tries);
            }));
        }
예제 #16
0
        /// <summary>
        /// Updates an existing key in the cache.
        /// <para>
        /// The cache manager will make sure the update will always happen on the most recent version.
        /// </para>
        /// <para>
        /// If version conflicts occur, if for example multiple cache clients try to write the same
        /// key, and during the update process, someone else changed the value for the key, the
        /// cache manager will retry the operation.
        /// </para>
        /// <para>
        /// The <paramref name="updateValue"/> function will get invoked on each retry with the most
        /// recent value which is stored in cache.
        /// </para>
        /// </summary>
        /// <param name="key">The key to update.</param>
        /// <param name="updateValue">The function to perform the update.</param>
        /// <param name="config">The cache configuration used to specify the update behavior.</param>
        /// <returns>The update result which is interpreted by the cache manager.</returns>
        /// <exception cref="System.ArgumentNullException">
        /// If key, updateValue or config are null.
        /// </exception>
        /// <remarks>
        /// If the cache does not use a distributed cache system. Update is doing exactly the same
        /// as Get plus Put.
        /// </remarks>
        public virtual UpdateItemResult <TCacheValue> Update(string key, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config)
        {
            if (updateValue == null)
            {
                throw new ArgumentNullException("updateValue");
            }

            var original = this.GetCacheItem(key);

            if (original == null)
            {
                return(UpdateItemResult.ForItemDidNotExist <TCacheValue>());
            }

            var value = updateValue(original.Value);

            this.Put(key, value);
            return(UpdateItemResult.ForSuccess <TCacheValue>(value));
        }