diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs --- a/services/identity/src/constants.rs +++ b/services/identity/src/constants.rs @@ -317,12 +317,9 @@ // Retry -// TODO: Replace this with `ExponentialBackoffConfig` from `comm-lib` pub mod retry { + // exponential backoff config pub const MAX_ATTEMPTS: usize = 8; - - pub const CONDITIONAL_CHECK_FAILED: &str = "ConditionalCheckFailed"; - pub const TRANSACTION_CONFLICT: &str = "TransactionConflict"; } // One-time keys diff --git a/services/identity/src/database/one_time_keys.rs b/services/identity/src/database/one_time_keys.rs --- a/services/identity/src/database/one_time_keys.rs +++ b/services/identity/src/database/one_time_keys.rs @@ -137,14 +137,15 @@ .send() .await; + use comm_lib::database::error_codes; match transaction { Ok(_) => return Ok((Some(otk_row.otk), requested_more_keys)), Err(e) => { info!("Error retrieving one-time key: {:?}", e); let dynamo_db_error = DynamoDBError::from(e); let retryable_codes = HashSet::from([ - retry::CONDITIONAL_CHECK_FAILED, - retry::TRANSACTION_CONFLICT, + error_codes::CONDITIONAL_CHECK_FAILED, + error_codes::TRANSACTION_CONFLICT, ]); if is_transaction_retryable(&dynamo_db_error, &retryable_codes) { info!("Encountered transaction conflict while retrieving one-time key - retrying"); @@ -324,11 +325,12 @@ .send() .await; + use comm_lib::database::error_codes::TRANSACTION_CONFLICT; match transaction { Ok(_) => break, Err(e) => { let dynamo_db_error = DynamoDBError::from(e); - let retryable_codes = HashSet::from([retry::TRANSACTION_CONFLICT]); + let retryable_codes = HashSet::from([TRANSACTION_CONFLICT]); if is_transaction_retryable(&dynamo_db_error, &retryable_codes) { info!("Encountered transaction conflict while uploading one-time keys - retrying"); exponential_backoff.sleep_and_retry().await?; diff --git a/services/identity/src/ddb_utils.rs b/services/identity/src/ddb_utils.rs --- a/services/identity/src/ddb_utils.rs +++ b/services/identity/src/ddb_utils.rs @@ -1,21 +1,14 @@ use chrono::{DateTime, Utc}; use comm_lib::{ - aws::{ - ddb::types::{ - error::TransactionCanceledException, AttributeValue, Put, - TransactWriteItem, Update, - }, - DynamoDBError, - }, + aws::ddb::types::{AttributeValue, Put, TransactWriteItem, Update}, database::{AttributeExtractor, AttributeMap}, }; -use once_cell::sync::Lazy; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::iter::IntoIterator; use crate::{ constants::{ - retry, USERS_TABLE_FARCASTER_ID_ATTRIBUTE_NAME, + USERS_TABLE_FARCASTER_ID_ATTRIBUTE_NAME, USERS_TABLE_SOCIAL_PROOF_ATTRIBUTE_NAME, USERS_TABLE_USERNAME_ATTRIBUTE, USERS_TABLE_WALLET_ADDRESS_ATTRIBUTE, }, @@ -23,6 +16,10 @@ siwe::SocialProof, }; +pub use comm_lib::database::{ + is_transaction_conflict, is_transaction_retryable, +}; + #[derive(Copy, Clone, Debug)] pub enum OlmAccountType { Content, @@ -228,44 +225,6 @@ } } -pub fn is_transaction_retryable( - err: &DynamoDBError, - retryable_codes: &HashSet<&str>, -) -> bool { - match err { - DynamoDBError::TransactionCanceledException( - TransactionCanceledException { - cancellation_reasons: Some(reasons), - .. - }, - ) => reasons.iter().any(|reason| { - retryable_codes.contains(&reason.code().unwrap_or_default()) - }), - _ => false, - } -} - -/// There are two error codes for operation failure due to already ongoing -/// transaction: -/// - `DynamoDBError::TransactionConflict` -/// - `DynamoDBError::TransactionCanceled` if `reason == "TransactionConflict"` -/// -/// The former is thrown in case of normal write operation -/// (WriteItem, UpdateItem, etc) when a transaction is modifying them -/// at the moment. -/// -/// The latter is thrown in transaction operation (TransactWriteItem) when -/// another transaction is modifying them at the moment. -pub fn is_transaction_conflict(err: &DynamoDBError) -> bool { - static RETRYABLE_CODES: Lazy> = - Lazy::new(|| HashSet::from([retry::TRANSACTION_CONFLICT])); - - match err { - DynamoDBError::TransactionConflictException(_) => true, - _ => is_transaction_retryable(err, &RETRYABLE_CODES), - } -} - #[cfg(test)] mod tests { use crate::constants::one_time_keys_table; diff --git a/shared/comm-lib/src/database.rs b/shared/comm-lib/src/database.rs --- a/shared/comm-lib/src/database.rs +++ b/shared/comm-lib/src/database.rs @@ -1,6 +1,7 @@ use aws_sdk_dynamodb::types::AttributeValue; pub use aws_sdk_dynamodb::Error as DynamoDBError; use chrono::{DateTime, Utc}; +use once_cell::sync::Lazy; use std::collections::HashSet; use std::fmt::{Display, Formatter}; use std::num::ParseIntError; @@ -26,6 +27,11 @@ // # Error handling +pub mod error_codes { + pub const CONDITIONAL_CHECK_FAILED: &str = "ConditionalCheckFailed"; + pub const TRANSACTION_CONFLICT: &str = "TransactionConflict"; +} + #[derive( Debug, derive_more::Display, derive_more::From, derive_more::Error, )] @@ -799,6 +805,46 @@ }) } +pub fn is_transaction_retryable( + err: &DynamoDBError, + retryable_codes: &HashSet<&str>, +) -> bool { + use aws_sdk_dynamodb::types::error::TransactionCanceledException; + + match err { + DynamoDBError::TransactionCanceledException( + TransactionCanceledException { + cancellation_reasons: Some(reasons), + .. + }, + ) => reasons.iter().any(|reason| { + retryable_codes.contains(&reason.code().unwrap_or_default()) + }), + _ => false, + } +} + +/// There are two error codes for operation failure due to already ongoing +/// transaction: +/// - `DynamoDBError::TransactionConflict` +/// - `DynamoDBError::TransactionCanceled` if `reason == "TransactionConflict"` +/// +/// The former is thrown in case of normal write operation +/// (WriteItem, UpdateItem, etc) when a transaction is modifying them +/// at the moment. +/// +/// The latter is thrown in transaction operation (TransactWriteItem) when +/// another transaction is modifying them at the moment. +pub fn is_transaction_conflict(err: &DynamoDBError) -> bool { + static RETRYABLE_CODES: Lazy> = + Lazy::new(|| HashSet::from([error_codes::TRANSACTION_CONFLICT])); + + match err { + DynamoDBError::TransactionConflictException(_) => true, + _ => is_transaction_retryable(err, &RETRYABLE_CODES), + } +} + #[cfg(test)] mod tests { use super::*;