-
-
Save fbatroni/48996995a82334e3634e259f74ef4799 to your computer and use it in GitHub Desktop.
AOP aspect to retry transaction on deadlock
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
package com.blogspot.lifeinide.postgres.locks; | |
import org.aspectj.lang.ProceedingJoinPoint; | |
import org.aspectj.lang.annotation.Around; | |
import org.aspectj.lang.annotation.Aspect; | |
import org.hibernate.exception.LockAcquisitionException; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.core.Ordered; | |
import org.springframework.stereotype.Component; | |
import org.springframework.transaction.support.TransactionSynchronizationManager; | |
@Aspect | |
@Component | |
public class DeadlockDetectAspect implements Ordered { | |
public static final Logger logger = LoggerFactory.getLogger(DeadlockDetectAspect.class); | |
/** Order for this aspect, should be lower than for transaction manager which has 100 **/ | |
protected int order = 99; | |
/** How many retries should be tried on deadlock **/ | |
protected int retryCount = 3; | |
/** How big is delay between deadlock retry (in ms) **/ | |
protected int delay = 1000; | |
@Around(value = "@annotation(org.springframework.transaction.annotation.Transactional)") | |
public Object methodRetry(ProceedingJoinPoint pjp) throws Throwable { | |
return detectDeadlocks(pjp); | |
} | |
@Around(value = "@within(org.springframework.transaction.annotation.Transactional)") | |
public Object classRetry(ProceedingJoinPoint pjp) throws Throwable { | |
return detectDeadlocks(pjp); | |
} | |
protected Object detectDeadlocks(ProceedingJoinPoint pjp) throws Throwable { | |
if (logger.isTraceEnabled()) | |
logger.trace("Before pointcut {} with transaction manager active: {}", | |
pjp.toString(), TransactionSynchronizationManager.isActualTransactionActive()); | |
try { | |
int retryCount = getRetryCount(); | |
while (true) { | |
try { | |
return pjp.proceed(); | |
} catch (LockAcquisitionException ex) { | |
// if transaction manager is active, this means that we are in nested @Transactional call, | |
// but we want only make retry for the main @Transactional call, that starts the transaction again | |
if (TransactionSynchronizationManager.isActualTransactionActive()) { | |
if (logger.isTraceEnabled()) | |
logger.trace("Deadlock pointcut detected, but transaction is still active - propagating"); | |
throw ex; | |
} else { | |
// end of retries? throw exception to upper layer | |
if (retryCount-- == 0) | |
throw ex; | |
// otherwise, try to repeat this step | |
if (logger.isDebugEnabled()) | |
logger.debug("Deadlock pointcut retry with retryCount={} (sleeping {} ms)", | |
retryCount, getDelay()); | |
Thread.sleep(getDelay()); | |
} | |
} | |
} | |
} finally { | |
if (logger.isTraceEnabled()) | |
logger.trace("After pointcut {} with transaction manager active: {}", | |
pjp.toString(), TransactionSynchronizationManager.isActualTransactionActive()); | |
} | |
} | |
@Override | |
public int getOrder() { | |
return order; | |
} | |
public void setOrder(int order) { | |
this.order = order; | |
} | |
public int getRetryCount() { | |
return retryCount; | |
} | |
public void setRetryCount(int retryCount) { | |
this.retryCount = retryCount; | |
} | |
public int getDelay() { | |
return delay; | |
} | |
public void setDelay(int delay) { | |
this.delay = delay; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment