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()); // act using (cache) { string value; var updateResult = cache.TryUpdate("key", updateFunc, 1, out value); // assert updateCalls.Should().Be(1, "should exit after the first item did not exist"); putCalls.Should().Be(0, "no put calls expected"); removeCalls.Should().Be(4, "item should be removed from others"); updateResult.Should().BeFalse(); } }
public async Task <UpdatePointResult> UpdatePointAsync(UpdatePointRequest updatePointRequest, CancellationToken cancellationToken = default(CancellationToken)) { if (updatePointRequest == null) { throw new ArgumentNullException("updatePointRequest"); } var geohash = S2Manager.GenerateGeohash(updatePointRequest.GeoPoint); var hashKey = S2Manager.GenerateHashKey(geohash, _config.HashKeyLength); var updateItemRequest = updatePointRequest.UpdateItemRequest; updateItemRequest.TableName = _config.TableName; var hashKeyValue = new AttributeValue { N = hashKey.ToString(CultureInfo.InvariantCulture) }; updateItemRequest.Key[_config.HashKeyAttributeName] = hashKeyValue; updateItemRequest.Key[_config.RangeKeyAttributeName] = updatePointRequest.RangeKeyValue; // Geohash and geoJson cannot be updated. updateItemRequest.AttributeUpdates.Remove(_config.GeohashAttributeName); updateItemRequest.AttributeUpdates.Remove(_config.GeoJsonAttributeName); UpdateItemResult updateItemResult = await _config.DynamoDBClient.UpdateItemAsync(updateItemRequest, cancellationToken).ConfigureAwait(false); var updatePointResult = new UpdatePointResult(updateItemResult); return(updatePointResult); }
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>(new CacheItem <string>("key", string.Empty), true, 100) }, putCalls: Enumerable.Repeat <Action>(() => putCalls++, 5).ToArray(), removeCalls: Enumerable.Repeat <Action>(() => removeCalls++, 5).ToArray()); // act using (cache) { string value; var updateResult = cache.TryUpdate("key", updateFunc, 1, 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>(new CacheItem <string>("key", string.Empty), true, 100), UpdateItemResult.ForSuccess <string>(new CacheItem <string>("key", string.Empty), true, 100), UpdateItemResult.ForSuccess <string>(new CacheItem <string>("key", 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()); // act using (cache) { string value; var updateResult = cache.TryUpdate("key", updateFunc, 1, out value); // assert updateCalls.Should().Be(1, "failed because item did not exist"); 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 UpdateItemResult_ForTooManyTries() { // arrange act Func <UpdateItemResult <object> > act = () => UpdateItemResult.ForTooManyRetries <object>(1001); // assert act().Should().BeEquivalentTo(new { Value = default(object), UpdateState = UpdateItemResultState.TooManyRetries, NumberOfTriesNeeded = 1001, VersionConflictOccurred = true }); }
public void UpdateItemResult_ForDidNotExist() { // arrange act Func <UpdateItemResult <object> > act = () => UpdateItemResult.ForItemDidNotExist <object>(); // assert act().Should().BeEquivalentTo(new { Value = default(object), UpdateState = UpdateItemResultState.ItemDidNotExist, NumberOfTriesNeeded = 1, VersionConflictOccurred = false }); }
public void UpdateItemResult_ForFactoryReturnsNull() { // arrange act Func <UpdateItemResult <object> > act = () => UpdateItemResult.ForFactoryReturnedNull <object>(); // assert act().Should().BeEquivalentTo(new { Value = default(object), UpdateState = UpdateItemResultState.FactoryReturnedNull, NumberOfTriesNeeded = 1, VersionConflictOccurred = false }); }
public void UpdateItemResult_ForSuccess() { // arrange act Func <UpdateItemResult <object> > act = () => UpdateItemResult.ForSuccess <object>("value", true, 1001); // assert act().ShouldBeEquivalentTo(new { Value = "value", UpdateState = UpdateItemResultState.Success, NumberOfTriesNeeded = 1001, VersionConflictOccurred = true }); }
public UpdatePointResult(UpdateItemResult updateItemResult) { if (updateItemResult == null) { throw new ArgumentNullException("updateItemResult"); } UpdateItemResult = updateItemResult; }
#pragma warning disable SA1600 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member protected UpdateItemResult <TCacheValue> UpdateNoScript(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { var committed = false; var tries = 0; var fullKey = GetKey(key, region); return(Retry(() => { do { tries++; var item = GetCacheItemInternal(key, region); if (item == null) { return UpdateItemResult.ForItemDidNotExist <TCacheValue>(); } ValidateExpirationTimeout(item); var oldValue = ToRedisValue(item.Value); var tran = _connection.Database.CreateTransaction(); tran.AddCondition(Condition.HashEqual(fullKey, HashFieldValue, oldValue)); // run update var newValue = updateValue(item.Value); // added null check, throw explicit to me more consistent. Otherwise it would throw later if (newValue == null) { return UpdateItemResult.ForFactoryReturnedNull <TCacheValue>(); } tran.HashSetAsync(fullKey, HashFieldValue, ToRedisValue(newValue)); committed = tran.Execute(); if (committed) { var newItem = item.WithValue(newValue); newItem.LastAccessedUtc = DateTime.UtcNow; if (newItem.ExpirationMode == ExpirationMode.Sliding && newItem.ExpirationTimeout != TimeSpan.Zero) { _connection.Database.KeyExpire(fullKey, newItem.ExpirationTimeout, CommandFlags.FireAndForget); } return UpdateItemResult.ForSuccess(newItem, tries > 1, tries); } Logger.LogDebug("Update of {0} {1} failed with version conflict, retrying {2}/{3}", key, region, tries, maxRetries); }while (committed == false && tries <= maxRetries); return UpdateItemResult.ForTooManyRetries <TCacheValue>(tries); })); }
/// <inheritdoc /> public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { if (!_isLuaAllowed) { return(UpdateNoScript(key, region, updateValue, maxRetries)); } var tries = 0; var fullKey = GetKey(key, region); return(Retry(() => { do { tries++; var item = GetCacheItemAndVersion(key, region, out int version); if (item == null) { return UpdateItemResult.ForItemDidNotExist <TCacheValue>(); } ValidateExpirationTimeout(item); // run update var newValue = updateValue(item.Value); // added null check, throw explicit to me more consistent. Otherwise it would throw within the script exec if (newValue == null) { return UpdateItemResult.ForFactoryReturnedNull <TCacheValue>(); } // resetting TTL on update, too var result = Eval(ScriptType.Update, fullKey, new[] { ToRedisValue(newValue), version, (int)item.ExpirationMode, (long)item.ExpirationTimeout.TotalMilliseconds, }); if (result != null && !result.IsNull) { // optimizing not retrieving the item again after update (could have changed already, too) var newItem = item.WithValue(newValue); newItem.LastAccessedUtc = DateTime.UtcNow; return UpdateItemResult.ForSuccess(newItem, tries > 1, tries); } Logger.LogDebug("Update of {0} {1} failed with version conflict, retrying {2}/{3}", key, region, tries, maxRetries); }while (tries <= maxRetries); return UpdateItemResult.ForTooManyRetries <TCacheValue>(tries); })); }
/// <inheritdoc /> public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { if (!this.isLuaAllowed) { return(this.UpdateNoScript(key, region, updateValue, maxRetries)); } var tries = 0; var fullKey = GetKey(key, region); return(this.Retry(() => { do { tries++; ////// actually slower than using the real value field, maybe suffers if the value is larger ////var version = this.Database.HashIncrement(fullKey, "version", 1L); ////var oldValueAndType = this.Database.HashGet(fullKey, new StackRedis.RedisValue[] { HashFieldValue, HashFieldType }); ////var oldValue = oldValueAndType[0]; ////var valueType = oldValueAndType[1]; ////if (oldValue.IsNull || !oldValue.HasValue || valueType.IsNull || !valueType.HasValue) ////{ //// return UpdateItemResult.ForItemDidNotExist<TCacheValue>(); ////} ////var newValue = updateValue( //// this.FromRedisValue(oldValue, valueType.ToString())); 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, fullKey, new[] { this.ToRedisValue(newValue), oldValue }); if (result != null && !result.IsNull) { return UpdateItemResult.ForSuccess <TCacheValue>(newValue, tries > 1, tries); } this.Logger.LogDebug("Update of {0} {1} failed with version conflict, retrying {2}/{3}", key, region, tries, maxRetries); }while (tries <= maxRetries); this.Logger.LogWarn("Update of {0} {1} failed with version conflict exiting because of too many retries.", key, region); return UpdateItemResult.ForTooManyRetries <TCacheValue>(tries); })); }
public void UpdateItemResult_ForSuccess() { // arrange act var item = new CacheItem <object>("key", new object()); Func <UpdateItemResult <object> > act = () => UpdateItemResult.ForSuccess <object>(item, true, 1001); // assert act().Should().BeEquivalentTo(new { Value = item, UpdateState = UpdateItemResultState.Success, NumberOfTriesNeeded = 1001, VersionConflictOccurred = true }); }
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."); } }
private UpdateItemResult <TCacheValue> Set(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { 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 <= 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 <= maxRetries); return(UpdateItemResult.ForTooManyRetries <TCacheValue>(tries)); }
public async Task ItemUpdate(int itemId, long count) { var result = new UpdateItemResult() { Items = new List <ItemInfoCls>() { new ItemInfoCls() { ItemId = itemId, Count = count } } }; await MsgMaker.SendMessage(WSResponseMsgID.UpdateItemResult, result); }
/// <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); })); }
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.ForItemDidNotExist <string>(), UpdateItemResult.ForSuccess(new CacheItem <string>("key", "some value"), true, 100) }, 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()); // act using (cache) { string value; var updateResult = cache.TryUpdate("key", updateFunc, 1, out value); // assert updateCalls.Should().Be(1, "first succeeds second fails"); putCalls.Should().Be(0, "no puts"); addCalls.Should().Be(0, "no adds"); removeCalls.Should().Be(4, "should remove from all others"); updateResult.Should().BeTrue("updated successfully."); } }
/// <inheritdoc /> public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { if (!this.isLuaAllowed) { return(this.UpdateNoScript(key, region, updateValue, maxRetries)); } 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, fullKey, new[] { this.ToRedisValue(newValue), oldValue }); if (result != null && !result.IsNull) { return UpdateItemResult.ForSuccess(newValue, tries > 1, tries); } this.Logger.LogDebug("Update of {0} {1} failed with version conflict, retrying {2}/{3}", key, region, tries, maxRetries); }while (tries <= maxRetries); this.Logger.LogWarn("Update of {0} {1} failed with version conflict exiting because of too many retries.", key, region); return UpdateItemResult.ForTooManyRetries <TCacheValue>(tries); })); }
public void CacheManager_Events_OnUpdate <T>(T cache) where T : ICacheManager <object> { using (cache) { // arrange var data = new EventCallbackData(); var key1 = Guid.NewGuid().ToString(); var key2 = Guid.NewGuid().ToString(); // all callbacks should be triggered, so result count should be 4 cache.OnUpdate += (sender, args) => data.AddCall(args, key1, key2); cache.OnPut += (sender, args) => data.AddCall(args, key1, key2); // this should not trigger cache.OnAdd += (sender, args) => data.AddCall(args, key1, key2); // we should have 3times add cache.OnGet += (sender, args) => data.AddCall(args, key1, key2); // this should not trigger cache.OnRemove += (sender, args) => data.AddCall(args, key1, key2); // this should not trigger // act get without region, should not return anything and should not trigger the event cache.Add(key1, 1, "region"); cache.Add(key2, 1, "region2"); cache.Add(key1, 1); cache.Update(key1, "region", o => ((int)o) + 1); cache.Update(key2, "region2", o => ((int)o) + 1); cache.Update(key1, o => ((int)o) + 1); // assert 4x Put calls x 3 event handles = 12 calls data.Calls.Should().Be(6, "we expect 6 hits"); data.Keys.ShouldAllBeEquivalentTo( new string[] { key1, key2, key1, key1, key2, key1 }, cfg => cfg.WithStrictOrdering(), "we expect 3 adds and 3 updates in exact order"); data.Regions.ShouldAllBeEquivalentTo( new string[] { "region", "region2", "region", "region2", }, cfg => cfg.WithStrictOrdering(), "we expect 4 region hits"); data.Results.ShouldAllBeEquivalentTo( Enumerable.Repeat(UpdateItemResult.ForSuccess <object>(2, false, 1), 3), "we expect exactly 3 update results with the same results"); } }
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(); } }
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_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(); } }
/// <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); })); }
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member protected UpdateItemResult <TCacheValue> UpdateNoScript(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { 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.connection.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); } }while (committed == false && tries <= maxRetries); return UpdateItemResult.ForTooManyRetries <TCacheValue>(tries); })); }
public async Task <UpdateItemResult> UpdateAsync(UpdateItemModel updateItemModel) { var authResult = await authenticatorsStore.UpdateItemModelAuthenticator.AuthenticateAsync(updateItemModel); if (!authResult.Succeed) { return(new UpdateItemResult(authResult)); } var validResult = await validatorsStore.UpdateItemModelValidator.ValidateAsync(updateItemModel); if (!validResult.Succeed) { return(new UpdateItemResult(validResult)); } var result = new UpdateItemResult(); var itemModel = mapper.Map <ItemModel>(updateItemModel); await tagsManager.AttachTagsToItemAsync(updateItemModel.Tags, itemModel.Id); await collectionsManager.AttachItemToCollection(itemModel.Id, updateItemModel.CollectionId); await itemsCrudService.UpdateAsync(itemModel); return(result); }
private UpdateItemResult <TCacheValue> Set(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { 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 <= maxRetries); // break operation if we cannot retrieve the object (maybe it has expired already). if (getStatus != StatusCode.Success || item == null || item.IsExpired) { return(UpdateItemResult.ForItemDidNotExist <TCacheValue>()); } var newValue = updateValue(item.Value); // added null check, throw explicit to me more consistent. Otherwise it would throw later eventually if (newValue == null) { return(UpdateItemResult.ForFactoryReturnedNull <TCacheValue>()); } item = item.WithValue(newValue); item.LastAccessedUtc = DateTime.UtcNow; if (item.ExpirationMode == ExpirationMode.Absolute || item.ExpirationMode == ExpirationMode.Sliding) { if (item.ExpirationTimeout > MaximumTimeout) { throw new InvalidOperationException($"Timeout must not exceed {MaximumTimeout.TotalDays} days."); } 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(item, tries > 1, tries)); } }while (!result.Success && result.StatusCode.HasValue && result.StatusCode.Value == 2 && tries <= maxRetries); return(UpdateItemResult.ForTooManyRetries <TCacheValue>(tries)); }
public override UpdateItemResult <TCacheValue> Update(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { return(UpdateItemResult.ForItemDidNotExist <TCacheValue>()); }
public Task <UpdateItemResult> ExecuteAsync(UpdateItemParameters parameters, CancellationToken cancellationToken = default) { return(Task.FromResult(UpdateItemResult.Ok("Hallo world"))); }
private UpdateItemResult <TCacheValue> Set(string key, string region, Func <TCacheValue, TCacheValue> updateValue, int maxRetries) { var fullyKey = GetKey(key, region); var tries = 0; IStoreOperationResult result; do { tries++; var getTries = 0; StatusCode getStatus; IGetOperationResult <CacheItem <TCacheValue> > getResult; CacheItem <TCacheValue> item; do { getTries++; getResult = Cache.ExecuteGet <CacheItem <TCacheValue> >(fullyKey); item = getResult.Value; getStatus = (StatusCode)(getResult.StatusCode ?? getResult.InnerResult?.StatusCode ?? -1); }while (ShouldRetry(getStatus) && getTries <= maxRetries); // break operation if we cannot retrieve the object (maybe it has expired already). if (!getResult.Success || item == null) { LogOperationResult(LogLevel.Warning, getResult, "Get item during update failed for '{0}'.", fullyKey); return(UpdateItemResult.ForItemDidNotExist <TCacheValue>()); } if (item.IsExpired) { return(UpdateItemResult.ForItemDidNotExist <TCacheValue>()); } var newValue = updateValue(item.Value); // added null check, throw explicit to me more consistent. Otherwise it would throw later eventually if (newValue == null) { return(UpdateItemResult.ForFactoryReturnedNull <TCacheValue>()); } item = item.WithValue(newValue); item.LastAccessedUtc = DateTime.UtcNow; if (item.ExpirationMode == ExpirationMode.Absolute || item.ExpirationMode == ExpirationMode.Sliding) { if (item.ExpirationTimeout > _maximumTimeout) { throw new InvalidOperationException($"Timeout must not exceed {_maximumTimeout.TotalDays} days."); } result = Cache.ExecuteCas(StoreMode.Set, fullyKey, item, item.ExpirationTimeout, getResult.Cas); } else { result = Cache.ExecuteCas(StoreMode.Set, fullyKey, item, getResult.Cas); } if (result.Success) { return(UpdateItemResult.ForSuccess(item, tries > 1, tries)); } else { LogOperationResult(LogLevel.Warning, result, "Update failed for '{0}'.", fullyKey); } WaitRetry(tries); }while (tries < maxRetries); return(UpdateItemResult.ForTooManyRetries <TCacheValue>(tries)); }