diff --git a/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManagerImpl.java b/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManagerImpl.java index 58e7c8c9b..892811245 100644 --- a/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManagerImpl.java +++ b/core/src/main/java/google/registry/persistence/transaction/JpaTransactionManagerImpl.java @@ -154,6 +154,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager { @Override public T transact(Supplier work) { + // This prevents inner transaction from retrying, thus avoiding a cascade retry effect. + if (inTransaction()) { + return transactNoRetry(work); + } return retrier.callWithRetry(() -> transactNoRetry(work), JpaRetries::isFailedTxnRetriable); } diff --git a/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerImplTest.java b/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerImplTest.java index 4daddd2b4..91ddd539e 100644 --- a/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerImplTest.java +++ b/core/src/test/java/google/registry/persistence/transaction/JpaTransactionManagerImplTest.java @@ -580,6 +580,32 @@ class JpaTransactionManagerImplTest { .isFalse()); } + @Test + void innerTransactions_noRetry() { + JpaTransactionManager spyJpaTm = spy(tm()); + doThrow(OptimisticLockException.class).when(spyJpaTm).delete(any(VKey.class)); + spyJpaTm.transact(() -> spyJpaTm.insert(theEntity)); + + Supplier supplier = + () -> { + Runnable work = () -> spyJpaTm.delete(theEntityKey); + work.run(); + return null; + }; + + assertThrows( + OptimisticLockException.class, + () -> + spyJpaTm.transact( + () -> { + spyJpaTm.exists(theEntity); + spyJpaTm.transact(supplier); + })); + + verify(spyJpaTm, times(3)).exists(theEntity); + verify(spyJpaTm, times(3)).delete(theEntityKey); + } + private void insertPerson(int age) { tm().getEntityManager() .createNativeQuery(String.format("INSERT INTO Person (age) VALUES (%d)", age))