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); } } }
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); } } }
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); } } }
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); } } } }