Example #1
0
        /// <summary>
        /// Read a value from the cache
        /// </summary>
        /// <typeparam name="T">Type to read</typeparam>
        /// <param name="key">Key</param>
        /// <param name="value">Value</param>
        /// <param name="notFound">Create T if not found, null to not do this. Item1 = value, Item2 = expiration.</param>
        public async Task <CachedItem <T> > Get <T>(string key, Func <Task <CachedItem <T> > > notFound) where T : class
        {
            using (var lockRead = cacheTimerLock.LockRead())
            {
                if (cache.TryGetValue(key, out KeyValuePair <DateTime, object> cacheValue))
                {
                    return(new CachedItem <T>((T)cacheValue.Value, cacheValue.Key));
                }
            }

            // most likely the callback needs to make a network request, so don't do it in a lock
            // it's ok if multiple calls stack on the same cache key, the last one to finish will win
            CachedItem <T> newItem = await notFound();

            // don't add null values to the cache
            if (newItem.Value != null)
            {
                using (var lockWrite = cacheTimerLock.LockWrite())
                {
                    cache[key] = new KeyValuePair <DateTime, object>(newItem.Expiration, newItem.Value);
                }
            }

            return(newItem);
        }
Example #2
0
        /// <summary>
        /// Get cache of symbols metadata and put into a dictionary. This method looks in the cache first, and if found, returns immediately, otherwise makes a network request and puts it in the cache
        /// </summary>
        /// <param name="api">Exchange API</param>
        /// <returns>Dictionary of symbol name and market, or null if there was an error</returns>
        public static async Task <Dictionary <string, ExchangeMarket> > GetExchangeMarketDictionaryFromCacheAsync(this ExchangeAPI api)
        {
            await new SynchronizationContextRemover();
            CachedItem <Dictionary <string, ExchangeMarket> > cacheResult = await api.Cache.Get <Dictionary <string, ExchangeMarket> >(nameof(GetExchangeMarketDictionaryFromCacheAsync), async() =>
            {
                try
                {
                    Dictionary <string, ExchangeMarket> symbolsMetadataDictionary = new Dictionary <string, ExchangeMarket>(StringComparer.OrdinalIgnoreCase);
                    IEnumerable <ExchangeMarket> symbolsMetadata = await api.GetMarketSymbolsMetadataAsync();

                    // build a new lookup dictionary
                    foreach (ExchangeMarket symbolMetadata in symbolsMetadata)
                    {
                        symbolsMetadataDictionary[symbolMetadata.MarketSymbol] = symbolMetadata;
                    }

                    // return the cached dictionary for 4 hours
                    return(new CachedItem <Dictionary <string, ExchangeMarket> >(symbolsMetadataDictionary, CryptoUtility.UtcNow.AddHours(4.0)));
                }
                catch// (Exception ex)
                {
                    // if the network goes down this could log quite a lot of exceptions...
                    //Logger.Error(ex);
                    return(new CachedItem <Dictionary <string, ExchangeMarket> >());
                }
            });

            if (cacheResult.Found)
            {
                return(cacheResult.Value);
            }
            return(null);
        }
Example #3
0
        /// <summary>
        /// Gets the exchange market from this exchange's SymbolsMetadata cache. This will make a network request if needed to retrieve fresh markets from the exchange using GetSymbolsMetadataAsync().
        /// Please note that sending a symbol that is not found over and over will result in many network requests. Only send symbols that you are confident exist on the exchange.
        /// </summary>
        /// <param name="marketSymbol">The market symbol. Ex. ADA/BTC. This is assumed to be normalized and already correct for the exchange.</param>
        /// <returns>The ExchangeMarket or null if it doesn't exist in the cache or there was an error</returns>
        public virtual async Task <ExchangeMarket> GetExchangeMarketFromCacheAsync(string marketSymbol)
        {
            try
            {
                // *NOTE*: custom caching, do not wrap in CacheMethodCall...
                // *NOTE*: vulnerability exists where if spammed with not found symbols, lots of network calls will happen, stalling the application
                // TODO: Add not found dictionary, or some mechanism to mitigate this risk
                // not sure if this is needed, but adding it just in case
                await new SynchronizationContextRemover();
                CachedItem <Dictionary <string, ExchangeMarket> > cacheResult = await Cache.Get <Dictionary <string, ExchangeMarket> >(nameof(GetExchangeMarketFromCacheAsync), null);

                if (cacheResult.Found && cacheResult.Value.TryGetValue(marketSymbol, out ExchangeMarket market))
                {
                    return(market);
                }

                // try again with a fresh request
                Cache.Remove(nameof(GetExchangeMarketFromCacheAsync));
                Cache.Remove(nameof(GetMarketSymbolsMetadataAsync));
                cacheResult = await Cache.Get <Dictionary <string, ExchangeMarket> >(nameof(GetExchangeMarketFromCacheAsync), async() =>
                {
                    Dictionary <string, ExchangeMarket> symbolsMetadataDictionary = new Dictionary <string, ExchangeMarket>(StringComparer.OrdinalIgnoreCase);
                    IEnumerable <ExchangeMarket> symbolsMetadata = await GetMarketSymbolsMetadataAsync();

                    // build a new lookup dictionary
                    foreach (ExchangeMarket symbolMetadata in symbolsMetadata)
                    {
                        symbolsMetadataDictionary[symbolMetadata.MarketSymbol] = symbolMetadata;
                    }

                    // return the cached dictionary for 4 hours
                    return(new CachedItem <Dictionary <string, ExchangeMarket> >(symbolsMetadataDictionary, CryptoUtility.UtcNow.AddHours(4.0)));
                });

                // attempt to lookup one more time in the dictionary
                if (cacheResult.Found && cacheResult.Value.TryGetValue(marketSymbol, out market))
                {
                    return(market);
                }
            }
            catch
            {
                // TODO: Report the error somehow, for now a failed network request will just return null symbol which will force the caller to use default handling
            }
            return(null);
        }
Example #4
0
        /// <summary>
        /// Read a value from the cache
        /// </summary>
        /// <typeparam name="T">Type to read</typeparam>
        /// <param name="key">Key</param>
        /// <param name="value">Value</param>
        /// <param name="notFound">Create T if not found, null to not do this. Item1 = value, Item2 = expiration.</param>
        public async Task <CachedItem <T> > GetOrCreate <T>(string key, Func <Task <CachedItem <T> > > notFound) where T : class
        {
            CachedItem <T> newItem = default;

            cacheTimerLock.EnterReadLock();
            try
            {
                if (cache.TryGetValue(key, out KeyValuePair <DateTime, object> cacheValue))
                {
                    return(new CachedItem <T>((T)cacheValue.Value, cacheValue.Key));
                }
            }
            finally
            {
                cacheTimerLock.ExitReadLock();
            }

            if (notFound == null)
            {
                return(new CachedItem <T>());
            }

            // most likely the callback needs to make a network request, so don't do it in a lock
            // it's ok if multiple calls stack on the same cache key, the last one to finish will win
            newItem = await notFound();

            // don't add null values to the cache
            if (newItem.Value != null)
            {
                cacheTimerLock.EnterWriteLock();
                try
                {
                    cache[key] = new KeyValuePair <DateTime, object>(newItem.Expiration, newItem.Value);
                }
                finally
                {
                    cacheTimerLock.ExitWriteLock();
                }
            }

            return(newItem);
        }