mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 18:17:13 +08:00 
			
		
		
		
	feat(core): add redis lock during insertion of event in event table during initial attempt of outgoing webhook delivery (#7579)
Co-authored-by: Debarati Ghatak <debarati.ghatak@Debarati-Ghatak-FW569NC29L.local>
This commit is contained in:
		| @ -2,6 +2,8 @@ use std::marker::PhantomData; | ||||
|  | ||||
| use common_utils::{errors::CustomResult, ext_traits::ValueExt}; | ||||
| use error_stack::ResultExt; | ||||
| use redis_interface as redis; | ||||
| use router_env::tracing; | ||||
|  | ||||
| use crate::{ | ||||
|     core::{ | ||||
| @ -9,6 +11,8 @@ use crate::{ | ||||
|         payments::helpers, | ||||
|     }, | ||||
|     db::{get_and_deserialize_key, StorageInterface}, | ||||
|     errors::RouterResult, | ||||
|     routes::app::SessionStateInfo, | ||||
|     services::logger, | ||||
|     types::{self, api, domain, PaymentAddress}, | ||||
|     SessionState, | ||||
| @ -154,3 +158,106 @@ pub(crate) fn get_idempotent_event_id( | ||||
| pub(crate) fn generate_event_id() -> String { | ||||
|     common_utils::generate_time_ordered_id("evt") | ||||
| } | ||||
|  | ||||
| const WEBHOOK_LOCK_PREFIX: &str = "WEBHOOK_LOCK"; | ||||
|  | ||||
| pub(super) async fn perform_redis_lock<A>( | ||||
|     state: &A, | ||||
|     unique_locking_key: &str, | ||||
|     merchant_id: common_utils::id_type::MerchantId, | ||||
| ) -> RouterResult<Option<String>> | ||||
| where | ||||
|     A: SessionStateInfo, | ||||
| { | ||||
|     let lock_value: String = uuid::Uuid::new_v4().to_string(); | ||||
|     let redis_conn = state | ||||
|         .store() | ||||
|         .get_redis_conn() | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Error connecting to redis")?; | ||||
|  | ||||
|     let redis_locking_key = format!( | ||||
|         "{}_{}_{}", | ||||
|         WEBHOOK_LOCK_PREFIX, | ||||
|         merchant_id.get_string_repr(), | ||||
|         unique_locking_key | ||||
|     ); | ||||
|     let redis_lock_expiry_seconds = state.conf().webhooks.redis_lock_expiry_seconds; | ||||
|  | ||||
|     let redis_lock_result = redis_conn | ||||
|         .set_key_if_not_exists_with_expiry( | ||||
|             &redis_locking_key.as_str().into(), | ||||
|             lock_value.clone(), | ||||
|             Some(i64::from(redis_lock_expiry_seconds)), | ||||
|         ) | ||||
|         .await; | ||||
|  | ||||
|     match redis_lock_result { | ||||
|         Ok(redis::SetnxReply::KeySet) => { | ||||
|             logger::info!("Lock acquired for for {redis_locking_key}"); | ||||
|             Ok(Some(lock_value)) | ||||
|         } | ||||
|         Ok(redis::SetnxReply::KeyNotSet) => { | ||||
|             logger::info!("Lock already held for {redis_locking_key}"); | ||||
|             Ok(None) | ||||
|         } | ||||
|         Err(err) => Err(err | ||||
|             .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|             .attach_printable("Error acquiring redis lock")), | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(super) async fn free_redis_lock<A>( | ||||
|     state: &A, | ||||
|     unique_locking_key: &str, | ||||
|     merchant_id: common_utils::id_type::MerchantId, | ||||
|     lock_value: Option<String>, | ||||
| ) -> RouterResult<()> | ||||
| where | ||||
|     A: SessionStateInfo, | ||||
| { | ||||
|     let redis_conn = state | ||||
|         .store() | ||||
|         .get_redis_conn() | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Error connecting to redis")?; | ||||
|  | ||||
|     let redis_locking_key = format!( | ||||
|         "{}_{}_{}", | ||||
|         WEBHOOK_LOCK_PREFIX, | ||||
|         merchant_id.get_string_repr(), | ||||
|         unique_locking_key | ||||
|     ); | ||||
|     match redis_conn | ||||
|         .get_key::<Option<String>>(&redis_locking_key.as_str().into()) | ||||
|         .await | ||||
|     { | ||||
|         Ok(val) => { | ||||
|             if val == lock_value { | ||||
|                 match redis_conn | ||||
|                     .delete_key(&redis_locking_key.as_str().into()) | ||||
|                     .await | ||||
|                 { | ||||
|                     Ok(redis::types::DelReply::KeyDeleted) => { | ||||
|                         logger::info!("Lock freed {redis_locking_key}"); | ||||
|                         tracing::Span::current().record("redis_lock_released", redis_locking_key); | ||||
|                         Ok(()) | ||||
|                     } | ||||
|                     Ok(redis::types::DelReply::KeyNotDeleted) => Err( | ||||
|                         errors::ApiErrorResponse::InternalServerError, | ||||
|                     ) | ||||
|                     .attach_printable("Status release lock called but key is not found in redis"), | ||||
|                     Err(error) => Err(error) | ||||
|                         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|                         .attach_printable("Error while deleting redis key"), | ||||
|                 } | ||||
|             } else { | ||||
|                 Err(errors::ApiErrorResponse::InternalServerError) | ||||
|                             .attach_printable("The redis value which acquired the lock is not equal to the redis value requesting for releasing the lock") | ||||
|             } | ||||
|         } | ||||
|         Err(error) => Err(error) | ||||
|             .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|             .attach_printable("Error while deleting redis key"), | ||||
|     } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Debarati Ghatak
					Debarati Ghatak