-
-
Save kai-niemi/cd6b4182a596e8afe02fb6173221a739 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A transaction boundary retry interceptor for container-managed transactions. | |
*/ | |
@TransactionBoundary | |
@Interceptor | |
@Priority(Interceptor.Priority.APPLICATION) | |
public class TransactionRetryInterceptor { | |
public static final int MAX_RETRY_ATTEMPTS = 10; | |
public static final int MAX_BACKOFF_TIME_MILLIS = 15000; | |
private static final ThreadLocalRandom RAND = ThreadLocalRandom.current(); | |
@PersistenceContext(unitName = "orderSystemPU") | |
private EntityManager entityManager; | |
@Inject | |
private TransactionService transactionService; | |
@Inject | |
private Logger logger; | |
@AroundInvoke | |
public Object aroundTransactionBoundary(InvocationContext ctx) throws Exception { | |
Assert.isFalse(entityManager.isJoinedToTransaction(), "Expected no transaction!"); | |
logger.info("Intercepting transactional method in retry loop: {}", ctx.getMethod().toGenericString()); | |
for (int attempt = 1; attempt < MAX_RETRY_ATTEMPTS; attempt++) { | |
try { | |
// Uses REQUIRES_NEW that will suspend current txn | |
Object rv = transactionService.executeWithinTransaction(ctx::proceed); | |
if (attempt > 1) { | |
logger.info("Recovered from transient error (attempt {}): {}", | |
attempt, ctx.getMethod().toGenericString()); | |
} else { | |
logger.info("Transactional method completed (attempt {}): {}", | |
attempt, ctx.getMethod().toGenericString()); | |
} | |
return rv; | |
} catch (Exception ex) { | |
Throwable t = ExceptionUtils.getMostSpecificCause(ex); | |
if (t instanceof SQLException) { | |
SQLException sqlException = (SQLException) t; | |
if (PSQLState.SERIALIZATION_FAILURE.getState().equals(sqlException.getSQLState())) { | |
long backoffMillis = Math.min((long) (Math.pow(2, attempt) + RAND.nextInt(0, 1000)), | |
MAX_BACKOFF_TIME_MILLIS); | |
logger.warn("Detected transient error (attempt {}) backoff for {}ms: {}", | |
attempt, backoffMillis, sqlException); | |
try { | |
Thread.sleep(backoffMillis); | |
} catch (InterruptedException e) { | |
Thread.currentThread().interrupt(); | |
} | |
} else { | |
logger.info("Detected non-transient error (propagating): {}", t.getMessage()); | |
throw ex; | |
} | |
} else { | |
logger.info("Detected non-transient error (propagating): {}", t.getMessage()); | |
throw ex; | |
} | |
} | |
} | |
throw new SQLTransactionRollbackException("Too many serialization conflicts - giving up retries!"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment