Esempio n. 1
0
        public async Task <RequestResult <T> > Run <T>(CloneableRequestMessage baseRequest, ExecuteRequestAsync <T> executeRequestAsync)
        {
            var         accessToken = GetAccessToken(baseRequest);
            LeakyBucket bucket      = null;

            if (accessToken != null)
            {
                bucket = _shopAccessTokenToLeakyBucket.GetOrAdd(accessToken, _ => new LeakyBucket());
            }

            while (true)
            {
                var request = baseRequest.Clone();

                if (accessToken != null)
                {
                    await bucket.GrantAsync();
                }

                try
                {
                    var fullResult = await executeRequestAsync(request);

                    var bucketState = LeakyBucketState.Get(fullResult.Response);

                    if (bucketState != null)
                    {
                        bucket?.SetState(bucketState);
                    }

                    return(fullResult);
                }
                catch (ShopifyRateLimitException ex) when(ex.Reason == ShopifyRateLimitReason.BucketFull || !_retryOnlyIfLeakyBucketFull)
                {
                    //Only retry if breach caused by full bucket
                    //Other limits will bubble the exception because it's not clear how long the program should wait
                    //Even if there is a Retry-After header, we probably don't want the thread to sleep for potentially many hours
                    //
                    //An exception may still occur:
                    //-Shopify may have a slightly different algorithm
                    //-Shopify may change to a different algorithm in the future
                    //-There may be timing and latency delays
                    //-Multiple programs may use the same access token
                    //-Multiple instances of the same program may use the same access token
                    await Task.Delay(THROTTLE_DELAY);
                }
            }
        }
Esempio n. 2
0
        public async Task <T> Run <T>(CloneableRequestMessage baseRequest, ExecuteRequestAsync <T> executeRequestAsync)
        {
            while (true)
            {
                var request = baseRequest.Clone();

                try
                {
                    var fullResult = await executeRequestAsync(request);

                    return(fullResult.Result);
                }
                catch (BizzebeeRateLimitException)
                {
                    await Task.Delay(RETRY_DELAY);
                }
            }
        }
Esempio n. 3
0
        public async Task <T> Run <T>(CloneableRequestMessage baseRequest, ExecuteRequestAsync <T> executeRequestAsync)
        {
            var         accessToken = GetAccessToken(baseRequest);
            LeakyBucket bucket      = null;

            if (accessToken != null)
            {
                bucket = _shopAccessTokenToLeakyBucket.GetOrAdd(accessToken, _ => new LeakyBucket());
            }

            while (true)
            {
                var request = baseRequest.Clone();

                if (accessToken != null)
                {
                    await bucket.GrantAsync();
                }

                try
                {
                    var fullResult = await executeRequestAsync(request);

                    var bucketState = GetBucketState(fullResult.Response);

                    if (bucketState != null)
                    {
                        bucket?.SetState(bucketState);
                    }

                    return(fullResult.Result);
                }
                catch (SquareSpaceRateLimitException)
                {
                    //An exception may still occur:
                    //-SquareSpace may have a slightly different algorithm
                    //-SquareSpace may change to a different algorithm in the future
                    //-There may be timing and latency delays
                    //-Multiple programs may use the same access token
                    //-Multiple instances of the same program may use the same access token
                    await Task.Delay(THROTTLE_DELAY);
                }
            }
        }
        public async Task <RequestResult <T> > Run <T>(CloneableRequestMessage baseRequest, ExecuteRequestAsync <T> executeRequestAsync)
        {
            while (true)
            {
                var request = baseRequest.Clone();

                try
                {
                    var fullResult = await executeRequestAsync(request);

                    return(fullResult);
                }
                catch (ShopifyRateLimitException ex) when(ex.Reason == ShopifyRateLimitReason.BucketFull || !_retryOnlyIfLeakyBucketFull)
                {
                    //Only retry if breach caused by full bucket
                    //Other limits will bubble the exception because it's not clear how long the program should wait
                    //Even if there is a Retry-After header, we probably don't want the thread to sleep for potentially many hours
                    await Task.Delay(RETRY_DELAY);
                }
            }
        }
Esempio n. 5
0
        public async Task <RequestResult <T> > Run <T>(CloneableRequestMessage baseRequest, ExecuteRequestAsync <T> executeRequestAsync, CancellationToken cancellationToken, int?graphqlQueryCost = null)
        {
            var  accessToken = GetAccessToken(baseRequest);
            var  bucket      = accessToken == null ? null : _shopAccessTokenToLeakyBucket.GetOrAdd(accessToken, _ => new MultiShopifyAPIBucket(_getRequestContext));
            bool isGraphQL   = baseRequest.RequestUri.AbsolutePath.EndsWith("graphql.json");

            while (true)
            {
                var request = baseRequest.Clone();

                if (isGraphQL)
                {
                    //if the user didn't pass a request query cost, we assume a cost of 50
                    graphqlQueryCost = graphqlQueryCost ?? 50;
                    if (bucket != null)
                    {
                        await bucket.WaitForAvailableGraphQLAsync(graphqlQueryCost.Value, cancellationToken);
                    }

                    var res = await executeRequestAsync(request);

                    var json = res.Result as JToken;

                    if (bucket != null)
                    {
                        var cost = json.SelectToken("extensions.cost");
                        if (cost != null)
                        {
                            var throttleStatus     = cost["throttleStatus"];
                            int maximumAvailable   = (int)throttleStatus["maximumAvailable"];
                            int restoreRate        = (int)throttleStatus["restoreRate"];
                            int currentlyAvailable = (int)throttleStatus["currentlyAvailable"];
                            int actualQueryCost    = (int?)cost["actualQueryCost"] ?? graphqlQueryCost.Value; //actual query cost is null if THROTTLED
                            int refund             = graphqlQueryCost.Value - actualQueryCost;                //may be negative if user didn't supply query cost
                            bucket.SetGraphQLBucketState(maximumAvailable, restoreRate, currentlyAvailable, refund);

                            //The user might have supplied no cost or an invalid cost
                            //We fix the query cost so the correct value is used if a retry is needed
                            graphqlQueryCost = (int)cost["requestedQueryCost"];
                        }
                    }

                    if (json.SelectToken("errors")
                        ?.Children()
                        .Any(r => r.SelectToken("extensions.code")?.Value <string>() == "THROTTLED")
                        == true)
                    {
                        await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);

                        continue;
                    }

                    return(res);
                }
                else //REST
                {
                    try
                    {
                        if (bucket != null)
                        {
                            await bucket.WaitForAvailableRESTAsync(cancellationToken);
                        }

                        var res = await executeRequestAsync(request);

                        if (bucket != null)
                        {
                            var apiCallLimitHeaderValue = GetRestCallLimit(res.Response);
                            if (apiCallLimitHeaderValue != null)
                            {
                                var split = apiCallLimitHeaderValue.Split('/');
                                if (split.Length == 2 && int.TryParse(split[0], out int currentlyUsed) &&
                                    int.TryParse(split[1], out int maxAvailable))
                                {
                                    bucket.SetRESTBucketState(maxAvailable, maxAvailable - currentlyUsed);
                                }
                            }
                        }

                        return(res);
                    }
                    catch (ShopifyRateLimitException ex) when(ex.Reason == ShopifyRateLimitReason.BucketFull || !_retryRESTOnlyIfLeakyBucketFull)
                    {
                        //Only retry if breach caused by full bucket
                        //Shopify sometimes return 429 for other limits (e.g if too many variants are created)
                        await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
                    }
                }
            }
        }