From ba719d684998b7bfd979a98c1f77322dd98d6cd7 Mon Sep 17 00:00:00 2001 From: Maor Barazani Date: Thu, 25 Jan 2024 14:04:09 +0200 Subject: [PATCH] extend retries for deployment status polling --- src/services/api-service.ts | 34 ++++++++++++++++++++++++++-------- src/services/push-service.ts | 5 +++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/services/api-service.ts b/src/services/api-service.ts index 2c12139..cc5bdef 100755 --- a/src/services/api-service.ts +++ b/src/services/api-service.ts @@ -2,7 +2,12 @@ import crypto from 'node:crypto'; import https from 'node:https'; import axios, { AxiosError } from 'axios'; -import { default as axiosRetry, isIdempotentRequestError, isNetworkError } from 'axios-retry'; +import { + IAxiosRetryConfig, + default as axiosRetry, + exponentialDelay, + isNetworkOrIdempotentRequestError, +} from 'axios-retry'; import { ZodObject } from 'zod/lib/types'; import { CONFIG_KEYS } from 'consts/config'; @@ -16,15 +21,26 @@ import logger from 'utils/logger'; const DEFAULT_TIMEOUT = 10 * 1000; -axiosRetry(axios, { - retries: 5, // number of retries - retryDelay: retryCount => retryCount * 1000, +/** + * Our default retry policy for axios-retry + * @see https://github.com/softonic/axios-retry?tab=readme-ov-file#usage + * shouldResetTimeout: if true, does not fail the entire process based on the time passed from the original request, but rather applies the timeout to each retry individually + * retryDelay: exponential delay with jitter, minimum 1 second before the 1st retry + * retryCondition: retry if network error (excluding CANCELED and ABORTED) or 5xx error on idempotent or safe requests (GET, HEAD, OPTIONS, PUT, DELETE) + */ +const DEFAULT_RETRY_POLICY: IAxiosRetryConfig = { + shouldResetTimeout: false, + retries: 3, + retryDelay: (...arg) => exponentialDelay(...arg, 1000), retryCondition: error => { - const retriableStatusCodes = [500, 502, 503, 504]; - const isRetriableStatusCode = error.response && retriableStatusCodes.includes(error.response.status); - return isRetriableStatusCode || isNetworkError(error) || isIdempotentRequestError(error); + return isNetworkOrIdempotentRequestError(error); }, -}); +}; + +const configureRetryPolicy = (customPolicy: IAxiosRetryConfig): void => { + const retryPolicy = { ...DEFAULT_RETRY_POLICY, ...customPolicy }; + axiosRetry(axios, retryPolicy); +}; const validateResponseIfError = (response: object, schemaValidator?: ZodObject): object => { if (schemaValidator) { @@ -70,8 +86,10 @@ const handleErrors = (error: any | Error | AxiosError): never => { export async function execute( params: ExecuteParams, schemaValidator?: ZodObject, + retryPolicy: IAxiosRetryConfig = {}, ): Promise { const DEBUG_TAG = 'api_service'; + configureRetryPolicy(retryPolicy); const accessToken = ConfigService.getConfigDataByKey(CONFIG_KEYS.ACCESS_TOKEN); if (!accessToken) { logger.error(ACCESS_TOKEN_NOT_FOUND); diff --git a/src/services/push-service.ts b/src/services/push-service.ts index e981879..0f3e9ce 100644 --- a/src/services/push-service.ts +++ b/src/services/push-service.ts @@ -68,7 +68,7 @@ export const uploadClientZipFile = async (appVersionId: number, buffer: Buffer) return response.data; }; -export const getAppVersionDeploymentStatus = async (appVersionId: number) => { +export const getAppVersionDeploymentStatus = async (appVersionId: number, extendedRetryPolicy: boolean = false) => { try { const baseAppVersionIdStatusUrl = getAppVersionDeploymentStatusUrl(appVersionId); const url = appsUrlBuilder(baseAppVersionIdStatusUrl); @@ -79,6 +79,7 @@ export const getAppVersionDeploymentStatus = async (appVersionId: number) => { method: HttpMethodTypes.GET, }, appVersionDeploymentStatusSchema, + extendedRetryPolicy ? { shouldResetTimeout: true, retries: 6 } : undefined, ); return response; } catch (error_: any | HttpError) { @@ -107,7 +108,7 @@ export const pollForDeploymentStatus = async ( DeploymentStatusTypesSchema['building-app'], DeploymentStatusTypesSchema['deploying-app'], ]; - const response = await getAppVersionDeploymentStatus(appVersionId); + const response = await getAppVersionDeploymentStatus(appVersionId, true); if (statusesToKeepPolling.includes(response.status)) { if (progressLogger) { progressLogger(response.status, response.tip);