From e93fce26b60b24301aa3fc3a0e87e56ec347816a Mon Sep 17 00:00:00 2001 From: Shivansh Mathur <104988143+ShivanshMathurJuspay@users.noreply.github.com> Date: Thu, 20 Mar 2025 19:23:04 +0530 Subject: [PATCH] fix(analytics): retry implementation for forex crate call (#7280) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Sandeep Kumar --- crates/router/src/consts.rs | 3 ++ crates/router/src/core/currency.rs | 48 ++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 0fce7048e6..d6872bcf06 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -229,3 +229,6 @@ pub(crate) const PROTOCOL: &str = "ECv2"; /// Sender ID for Google Pay Decryption pub(crate) const SENDER_ID: &[u8] = b"Google"; + +/// Default value for the number of attempts to retry fetching forex rates +pub const DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS: u64 = 3; diff --git a/crates/router/src/core/currency.rs b/crates/router/src/core/currency.rs index bc63dc878c..43b63cd6e6 100644 --- a/crates/router/src/core/currency.rs +++ b/crates/router/src/core/currency.rs @@ -2,11 +2,13 @@ use analytics::errors::AnalyticsError; use common_utils::errors::CustomResult; use currency_conversion::types::ExchangeRates; use error_stack::ResultExt; +use router_env::logger; use crate::{ + consts::DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS, core::errors::ApiErrorResponse, services::ApplicationResponse, - utils::currency::{self, convert_currency, get_forex_rates}, + utils::currency::{self, convert_currency, get_forex_rates, ForexError as ForexCacheError}, SessionState, }; @@ -48,9 +50,45 @@ pub async fn get_forex_exchange_rates( state: SessionState, ) -> CustomResult { let forex_api = state.conf.forex_api.get_inner(); - let rates = get_forex_rates(&state, forex_api.data_expiration_delay_in_seconds) - .await - .change_context(AnalyticsError::ForexFetchFailed)?; + let mut attempt = 1; - Ok((*rates.data).clone()) + logger::info!("Starting forex exchange rates fetch"); + loop { + logger::info!("Attempting to fetch forex rates - Attempt {attempt} of {DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS}"); + + match get_forex_rates(&state, forex_api.data_expiration_delay_in_seconds).await { + Ok(rates) => { + logger::info!("Successfully fetched forex rates"); + return Ok((*rates.data).clone()); + } + Err(error) => { + let is_retryable = matches!( + error.current_context(), + ForexCacheError::CouldNotAcquireLock + | ForexCacheError::EntryNotFound + | ForexCacheError::ForexDataUnavailable + | ForexCacheError::LocalReadError + | ForexCacheError::LocalWriteError + | ForexCacheError::RedisConnectionError + | ForexCacheError::RedisLockReleaseFailed + | ForexCacheError::RedisWriteError + | ForexCacheError::WriteLockNotAcquired + ); + + if !is_retryable { + return Err(error.change_context(AnalyticsError::ForexFetchFailed)); + } + + if attempt >= DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS { + logger::error!("Failed to fetch forex rates after {DEFAULT_ANALYTICS_FOREX_RETRY_ATTEMPTS} attempts"); + return Err(error.change_context(AnalyticsError::ForexFetchFailed)); + } + logger::warn!( + "Forex rates fetch failed with retryable error, retrying in {attempt} seconds" + ); + tokio::time::sleep(std::time::Duration::from_secs(attempt * 2)).await; + attempt += 1; + } + } + } }