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;
 }
Beispiel #3
0
        /// <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;
            }
        }
Beispiel #4
0
        /// <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);
            }
        }
Beispiel #5
0
        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);
        }
Beispiel #9
0
        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);
        }
Beispiel #11
0
        public async Task <IVersionedData> UpsertInternalAsync(IVersionedDataKind kind, IVersionedData item)
        {
            await ArbitraryTask();

            return(UpsertInternal(kind, item));
        }
Beispiel #12
0
        public override IEnumerable <string> GetDependencyKeys(IVersionedData item)
        {
            var ps = ((item as FeatureFlag).Prerequisites) ?? Enumerable.Empty <Prerequisite>();

            return(from p in ps select p.Key);
        }
Beispiel #13
0
 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++;
 }
Beispiel #16
0
        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));
 }
Beispiel #18
0
 public IVersionedData UpsertInternal(IVersionedDataKind kind, IVersionedData item)
 {
     return(WaitSafely(() => _coreAsync.UpsertInternalAsync(kind, item)));
 }