Translate TransactionCanceledException to ContestedOptimisticLockException in Accounts#changeNumber

This commit is contained in:
Chris Eager 2024-01-30 15:49:27 -06:00 committed by Chris Eager
parent 84af984c4b
commit 2d4d37f96a
2 changed files with 53 additions and 0 deletions

View File

@ -429,6 +429,24 @@ public class Accounts extends AbstractDynamoDbStore {
succeeded = true;
} catch (final JsonProcessingException e) {
throw new IllegalArgumentException(e);
} catch (final TransactionCanceledException e) {
if (e.hasCancellationReasons() && e.cancellationReasons().size() == 6) {
// the cancellation reasons map to the write items:
// 0. phone number constraint delete - no conditions
// 1. phone number constraint put - conditional on the key not existing (it is deleted by 0)
// 2. pni constraint delete - no conditions
// 3. pni constraint put - conditional on the key not existing (it is deleted by 2)
// 4. deleted accounts delete - no conditions
// 5. account update - conditional on #version = :version
if (CONDITIONAL_CHECK_FAILED.equals(e.cancellationReasons().get(5).code())) {
// the #version = :version condition failed, which indicates a concurrent update
throw new ContestedOptimisticLockException();
}
} else {
log.warn("Unexpected cancellation reasons: {}", e.cancellationReasons());
}
throw e;
} finally {
if (!succeeded) {
account.setNumber(originalNumber, originalPni);

View File

@ -885,6 +885,41 @@ class AccountsTest {
assertThrows(TransactionCanceledException.class, () -> accounts.changeNumber(account, targetNumber, existingPhoneNumberIdentifier, Optional.empty(), Collections.emptyList()));
}
@Test
public void testChangeNumberContestedOptimisticLock() {
final String originalNumber = "+14151112222";
final String targetNumber = "+14151113333";
final UUID originalPni = UUID.randomUUID();
final UUID targetPni = UUID.randomUUID();
final Device device = generateDevice(DEVICE_ID_1);
final Account firstAccountInstance = generateAccount(originalNumber, UUID.randomUUID(), originalPni,
List.of(device));
createAccount(firstAccountInstance);
final Account secondAccountInstance = accounts.getByAccountIdentifier(firstAccountInstance.getUuid()).orElseThrow();
// update via the first instance, which will update the version
firstAccountInstance.setCurrentProfileVersion("1");
accounts.update(firstAccountInstance);
assertThrows(ContestedOptimisticLockException.class,
() -> accounts.changeNumber(secondAccountInstance, targetNumber, targetPni, Optional.empty(),
Collections.emptyList()), "Second account instance has stale version");
final Account refreshedAccountInstance = accounts.getByAccountIdentifier(firstAccountInstance.getUuid())
.orElseThrow();
accounts.changeNumber(refreshedAccountInstance, targetNumber, targetPni, Optional.empty(),
Collections.emptyList());
assertPhoneNumberConstraintDoesNotExist(originalNumber);
assertPhoneNumberIdentifierConstraintDoesNotExist(originalPni);
assertPhoneNumberConstraintExists(targetNumber, firstAccountInstance.getUuid());
assertPhoneNumberIdentifierConstraintExists(targetPni, firstAccountInstance.getUuid());
}
@Test
void testSwitchUsernameHashes() {
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());