package org.openstack.atlas.service.domain.deadlock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.hibernate.JDBCException;
import org.hibernate.SessionFactory;
import org.hibernate.dialect.Dialect;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.engine.SessionFactoryImplementor;
import org.openstack.atlas.service.domain.util.DeepCopy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import org.openstack.atlas.util.debug.Debug;
/**
* This Aspect will cause methods to retry if there is a notion of a deadlock.
* <p/>
* <emf>Note that the aspect implements the Ordered interface so we can set the
* precedence of the aspect higher than the transaction advice (we want a fresh
* transaction each time we retry). Also note that all aspects are singletons.
* </emf>
*/
@Aspect
@Component
public class DeadLockRetryAspect implements Ordered {
private static final Logger LOGGER = LoggerFactory.getLogger(DeadLockRetryAspect.class);
private int order = 99; // Transaction manager order should be set to 100
@PersistenceContext(unitName = "loadbalancing")
private EntityManager entityManager;
private int conncurrencyRetryCalls = 0;
public int getConncurrencyRetryCalls() {
return conncurrencyRetryCalls;
}
/**
* Deadlock retry. The aspect applies to every service method with the
* annotation {@link DeadLockRetry}
*
* @param pjp the joinpoint
* @param deadLockRetry the concurrency retry
* @return
* @throws Throwable the throwable
*/
@Around(value = "@annotation(deadLockRetry)", argNames = "pjp,deadLockRetry")
public Object concurrencyRetry(final ProceedingJoinPoint pjp, final DeadLockRetry deadLockRetry) throws Throwable {
conncurrencyRetryCalls++;
final Integer retryCount = deadLockRetry.retryCount();
Integer deadlockCounter = 0;
Object result = null;
while (deadlockCounter < retryCount) {
try {
Object[] argsCopy = copyArgs(pjp.getArgs()); // copy original args so side effects aren't introduced
result = pjp.proceed(argsCopy);
break;
} catch (final JpaSystemException exception) {
if (exception.getCause() instanceof PersistenceException) {
deadlockCounter = handleException((PersistenceException) exception.getCause(), deadlockCounter, retryCount);
} else {
throw exception;
}
} catch (final PersistenceException pe) {
deadlockCounter = handleException(pe, deadlockCounter, retryCount);
} catch (Exception e) {
String excMessage = Debug.getExtendedStackTrace(e);
LOGGER.error(e.getMessage() + " " + excMessage, e);
throw e;
}
}
return result;
}
private Object[] copyArgs(Object[] args) {
Object[] copy = new Object[args.length];
for (int i = 0; i < args.length; i++) {
copy[i] = DeepCopy.copy(args[i]);
}
return copy;
}
/**
* handles the persistence exception. Performs checks to see if the
* exception is a deadlock and check the retry count.
*
* @param exception the persistence exception that could be a deadlock
* @param deadlockCounter the counter of occured deadlocks
* @param retryCount the max retry count
* @return the deadlockCounter that is incremented
*/
private Integer handleException(final PersistenceException exception, Integer deadlockCounter, final Integer retryCount) {
if (isDeadlock(exception)) {
deadlockCounter++;
LOGGER.error("Deadlocked ", exception.getMessage());
if (deadlockCounter == (retryCount - 1)) {
throw exception;
}
} else {
throw exception;
}
return deadlockCounter;
}
/**
* check if the exception is a deadlock error.
*
* @param exception the persitence error
* @return is a deadlock error
*/
private Boolean isDeadlock(final PersistenceException exception) {
Boolean isDeadlock = Boolean.FALSE;
final Dialect dialect = getDialect();
if (dialect instanceof ErrorCodeAware && exception.getCause() instanceof JDBCException) {
if (((ErrorCodeAware) dialect).getDeadlockErrorCodes().contains(getSQLErrorCode(exception))) {
isDeadlock = Boolean.TRUE;
}
}
return isDeadlock;
}
/**
* Returns the currently used dialect
*
* @return the dialect
*/
private Dialect getDialect() {
final SessionFactory sessionFactory = ((HibernateEntityManagerFactory) entityManager.getEntityManagerFactory()).getSessionFactory();
Dialect dialect = ((SessionFactoryImplementor) sessionFactory).getDialect();
if (dialect instanceof org.hibernate.dialect.MySQL5InnoDBDialect) {
return new MySQL5InnoDBDialect();
}
// Add custom dialect conditionals here.
return dialect;
}
/**
* extracts the low level sql error code from the
* {@link PersistenceException}
*
* @param exception the persistence exception
* @return the low level sql error code
*/
private int getSQLErrorCode(final PersistenceException exception) {
return ((JDBCException) exception.getCause()).getSQLException().getErrorCode();
}
/**
* {@inheritDoc}
*/
public int getOrder() {
return order;
}
/**
* Sets the order.
*
* @param order the order to set
*/
public void setOrder(final int order) {
this.order = order;
}
}