/// <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; }
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."); } }
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(); } }
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."); } }
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(); } }
/// <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); })); }
/// <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);
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)); }
/// <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)); }
public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, UpdateItemConfig config) { this.UpdateCall(); return(this.UpdateValue); }
/// <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)); }
/// <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)); }
/// <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); })); }
/// <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)); }