private void AddItem(IDictionary <IVersionedDataKind, IDictionary <string, IVersionedData> > allData, IVersionedDataKind kind, IVersionedData item) { IDictionary <string, IVersionedData> items; if (!allData.TryGetValue(kind, out items)) { items = new Dictionary <string, IVersionedData>(); allData[kind] = items; } if (items.ContainsKey(item.Key)) { switch (_duplicateKeysHandling) { case DuplicateKeysHandling.Throw: throw new System.Exception("in \"" + kind.GetNamespace() + "\", key \"" + item.Key + "\" was already defined"); case DuplicateKeysHandling.Ignore: break; default: throw new NotImplementedException("Unknown duplicate keys handling: " + _duplicateKeysHandling); } } else { items[item.Key] = item; } }
public void ForceSet(IVersionedDataKind kind, IVersionedData item) { if (!Data.ContainsKey(kind)) { Data[kind] = new Dictionary <string, IVersionedData>(); } Data[kind][item.Key] = item; }
/// <inheritdoc/> public void Upsert <T>(VersionedDataKind <T> kind, T item) where T : IVersionedData { Exception failure = null; IVersionedData newState = item; try { newState = _core.UpsertInternal(kind, item); } catch (Exception e) { // Normally, if the underlying store failed to do the update, we do not want to update the cache - // the idea being that it's better to stay in a consistent state of having old data than to act // like we have new data but then suddenly fall back to old data when the cache expires. However, // if the cache TTL is infinite, then it makes sense to update the cache always. if (!_caching.IsInfiniteTtl) { throw; } failure = e; } if (_itemCache != null) { _itemCache.Set(new CacheKey(kind, item.Key), newState); } if (_allCache != null) { // If the cache has a finite TTL, then we should remove the "all items" cache entry to force // a reread the next time All is called. However, if it's an infinite TTL, we need to just // update the item within the existing "all items" entry (since we want things to still work // even if the underlying store is unavailable). if (_caching.IsInfiniteTtl) { try { var cachedAll = _allCache.Get(kind); _allCache.Set(kind, cachedAll.SetItem(item.Key, newState)); } catch (Exception) { } // An exception here means that we did not have a cached value for All, so it tried to query // the underlying store, which failed (not surprisingly since it just failed a moment ago // when we tried to do an update). This should not happen in infinite-cache mode, but if it // does happen, there isn't really anything we can do. } else { _allCache.Remove(kind); } } if (failure != null) { throw failure; } }
/// <summary> /// <see cref="IFeatureStore.Upsert"/> /// </summary> public void Upsert <T>(VersionedDataKind <T> kind, T item) where T : IVersionedData { IVersionedData newState = _core.UpsertInternal(kind, item); if (_itemCache != null) { _itemCache.Set(new CacheKey(kind, item.Key), newState); } if (_allCache != null) { _allCache.Remove(kind); } }
public IDictionary <string, IVersionedData> GetAllInternal(IVersionedDataKind kind) { IDatabase db = _redis.GetDatabase(); HashEntry[] allEntries = db.HashGetAll(ItemsKey(kind)); Dictionary <string, IVersionedData> result = new Dictionary <string, IVersionedData>(); foreach (HashEntry entry in allEntries) { IVersionedData item = FeatureStoreHelpers.UnmarshalJson(kind, entry.Value); result[item.Key] = item; } return(result); }
private static void AddWithDependenciesFirst(IVersionedData item, IDictionary <string, IVersionedData> remainingItems, IVersionedDataOrdering ordering, OutputOrdering output) { remainingItems.Remove(item.Key); // we won't need to visit this item again foreach (var prereqKey in ordering.GetDependencyKeys(item)) { if (remainingItems.TryGetValue(prereqKey, out var prereqItem)) { AddWithDependenciesFirst(prereqItem, remainingItems, ordering, output); } } output.Add(item); }
public IVersionedData UpsertInternal(IVersionedDataKind kind, IVersionedData item) { if (!Data.ContainsKey(kind)) { Data[kind] = new Dictionary <string, IVersionedData>(); } if (Data[kind].TryGetValue(item.Key, out var oldItem)) { if (oldItem.Version >= item.Version) { return(oldItem); } } Data[kind][item.Key] = item; return(item); }
public void AddToData_DuplicateKeysHandling_Throw() { const string key = "flag1"; FeatureFlag initialFeatureFlag = new FeatureFlag(key, version: 0, deleted: false); Dictionary <IVersionedDataKind, IDictionary <string, IVersionedData> > data = new Dictionary <IVersionedDataKind, IDictionary <string, IVersionedData> > { { VersionedDataKind.Features, new Dictionary <string, IVersionedData> { { key, initialFeatureFlag } } } }; FlagFileData fileData = new FlagFileData { Flags = new Dictionary <string, JToken> { { key, new JObject( new JProperty("key", key), new JProperty("version", 1) ) } } }; FlagFileDataMerger merger = new FlagFileDataMerger(DuplicateKeysHandling.Throw); Exception err = Assert.Throws <Exception>(() => { merger.AddToData(fileData, data); }); Assert.Equal("in \"features\", key \"flag1\" was already defined", err.Message); IVersionedData postFeatureFlag = data[VersionedDataKind.Features][key]; Assert.Same(initialFeatureFlag, postFeatureFlag); Assert.Equal(0, postFeatureFlag.Version); }
private void AddItem(IDictionary <IVersionedDataKind, IDictionary <string, IVersionedData> > allData, IVersionedDataKind kind, IVersionedData item) { IDictionary <string, IVersionedData> items; if (!allData.TryGetValue(kind, out items)) { items = new Dictionary <string, IVersionedData>(); allData[kind] = items; } if (items.ContainsKey(item.Key)) { throw new System.Exception("in \"" + kind.GetNamespace() + "\", key \"" + item.Key + "\" was already defined"); } items[item.Key] = item; }
public void AddToData_DuplicateKeysHandling_Ignore() { const string key = "flag1"; FeatureFlag initialFeatureFlag = new FeatureFlag(key, version: 0, deleted: false); Dictionary <IVersionedDataKind, IDictionary <string, IVersionedData> > data = new Dictionary <IVersionedDataKind, IDictionary <string, IVersionedData> > { { VersionedDataKind.Features, new Dictionary <string, IVersionedData> { { key, initialFeatureFlag } } } }; FlagFileData fileData = new FlagFileData { Flags = new Dictionary <string, JToken> { { key, new JObject( new JProperty("key", key), new JProperty("version", 1) ) } } }; FlagFileDataMerger merger = new FlagFileDataMerger(DuplicateKeysHandling.Ignore); merger.AddToData(fileData, data); IVersionedData postFeatureFlag = data[VersionedDataKind.Features][key]; Assert.Same(initialFeatureFlag, postFeatureFlag); Assert.Equal(0, postFeatureFlag.Version); }
public async Task <IVersionedData> UpsertInternalAsync(IVersionedDataKind kind, IVersionedData item) { await ArbitraryTask(); return(UpsertInternal(kind, item)); }
public override IEnumerable <string> GetDependencyKeys(IVersionedData item) { var ps = ((item as FeatureFlag).Prerequisites) ?? Enumerable.Empty <Prerequisite>(); return(from p in ps select p.Key); }
public virtual IEnumerable <string> GetDependencyKeys(IVersionedData item) { return(Enumerable.Empty <string>()); }
/// <summary> /// Marshals a feature store item into a JSON string. This is a convenience method for /// feature store implementations, so that they can use the same JSON library that is used /// within the LaunchDarkly SDK rather than importing one themselves. All of the storeable /// classes used by the SDK are guaranteed to support this type of serialization. /// </summary> /// <param name="item">the item to be marshaled</param> /// <returns>the JSON string</returns> public static string MarshalJson(IVersionedData item) { return(JsonConvert.SerializeObject(item)); }
internal void Add(IVersionedData item) { _ordering[item.Key] = _index++; }
public IVersionedData UpsertInternal(IVersionedDataKind kind, IVersionedData newItem) { IDatabase db = _redis.GetDatabase(); string baseKey = ItemsKey(kind); while (true) { string oldJson; try { oldJson = db.HashGet(baseKey, newItem.Key); } catch (RedisTimeoutException e) { Log.ErrorFormat("Timeout in update when reading {0} from {1}: {2}", newItem.Key, baseKey, e.ToString()); throw; } IVersionedData oldItem = (oldJson == null) ? null : FeatureStoreHelpers.UnmarshalJson(kind, oldJson); int oldVersion = (oldJson == null) ? -1 : oldItem.Version; if (oldVersion >= newItem.Version) { Log.DebugFormat("Attempted to {0} key: {1} version: {2} with a version that is" + " the same or older: {3} in \"{4}\"", newItem.Deleted ? "delete" : "update", newItem.Key, oldVersion, newItem.Version, kind.GetNamespace()); return(oldItem); } // This hook is used only in unit tests _updateHook?.Invoke(); // Note that transactions work a bit differently in StackExchange.Redis than in other // Redis clients. The same Redis connection is shared across all threads, so it can't // set a WATCH at the moment we start the transaction. Instead, it saves up all of // the actions we send during the transaction, and replays them all within a MULTI // when the transaction. AddCondition() is this client's way of doing a WATCH, and it // can only refer to the whole value, not to a JSON property of the value; that's why // we kept track of the whole value in "oldJson". ITransaction txn = db.CreateTransaction(); txn.AddCondition(oldJson == null ? Condition.HashNotExists(baseKey, newItem.Key) : Condition.HashEqual(baseKey, newItem.Key, oldJson)); txn.HashSetAsync(baseKey, newItem.Key, JsonConvert.SerializeObject(newItem)); try { bool success = txn.Execute(); if (!success) { // The watch was triggered, we should retry Log.Debug("Concurrent modification detected, retrying"); continue; } } catch (RedisTimeoutException e) { Log.ErrorFormat("Timeout on update of {0} in {1}: {2}", newItem.Key, baseKey, e.ToString()); throw; } return(newItem); } }
/// <summary> /// Marshals a feature store item into a JSON string. This is a convenience method for /// feature store implementations, so that they can use the same JSON library that is used /// within the LaunchDarkly SDK rather than importing one themselves. All of the storeable /// classes used by the SDK are guaranteed to support this type of serialization. /// </summary> /// <param name="item">the item to be marshaled</param> /// <returns>the JSON string</returns> public static string MarshalJson(IVersionedData item) { return(JsonUtil.EncodeJson(item)); }
public IVersionedData UpsertInternal(IVersionedDataKind kind, IVersionedData item) { return(WaitSafely(() => _coreAsync.UpsertInternalAsync(kind, item))); }