Google

Aug 28, 2013

Deadlock Retry with JDK Dynamic Proxy

Here are the key steps in writing a dead lock retry service with JDK Dynamic Proxy.

Step 1: Define the custom deadlock retry annotation.

package com.myapp.deadlock;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface DeadlockRetry
{
    int maxTries() default 10;
    
    int tryIntervalMillis() default 1000;
}



Step 2: Define the JDK dynamic proxy class.



package com.myapp.deadlock;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class DeadlockRetryHandler implements InvocationHandler
{
    
    private static final Logger LOG = LoggerFactory.getLogger(DeadlockRetryHandler.class);
    
    private Object target;
    
    public DeadlockRetryHandler(Object target)
    {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    {
        
        Annotation[] annotations = method.getAnnotations();
        
        DeadlockRetry deadlockRetry = (DeadlockRetry) annotations[0];
        
        final Integer maxTries = deadlockRetry.maxTries();
        long tryIntervalMillis = deadlockRetry.tryIntervalMillis();
        
        int count = 0;
        
        do
        {
            try
            {
                count++;
                LOG.info("Attempting to invoke method " + method.getName() + " on " + target.getClass() + " count "
                        + count);
                Object result = method.invoke(target, args);    // retry
                LOG.info("Completed invocation of method " + method.getName() + " on " + target.getClass());
                return result;
            }
            catch (Throwable e)
            {
                if (!DeadlockUtil.isDeadLock(e))
                {
                    throw new RuntimeException(e);
                }
                
                if (tryIntervalMillis > 0)
                {
                    try
                    {
                        Thread.sleep(tryIntervalMillis);
                    }
                    catch (InterruptedException ie)
                    {
                        LOG.warn("Deadlock retry thread interrupted", ie);
                    }
                }
            }
        }
        while (count <= maxTries);
        
        //gets here only when all attempts have failed
        throw new RuntimeException("DeadlockRetryMethodInterceptor failed to successfully execute target "
                + " due to deadlock in all retry attempts",
                new DeadlockDataAccessException("Created by DeadlockRetryMethodInterceptor", null));
        
    }
    
}


Step 3: The utility class used by the dynamic proxy to determine if the exception indicates deadlock.

package com.myapp.deadlock;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.springframework.dao.CannotAcquireLockException;

public final class DeadlockUtil
{  
    public static final String DEADLOCK_MSG = "encountered a deadlock situation. Please re-run your command.";
    
    static boolean isDeadLock(Throwable throwable)
    {
        boolean isDeadLock = false;
        
        Throwable[] causes = ExceptionUtils.getThrowables(throwable);
        for (Throwable cause : causes)
        {
            if (cause instanceof CannotAcquireLockException || (cause.getMessage() != null
                    && (cause.getMessage().contains("LockAcquisitionException") || cause.getMessage().contains(
                    DEADLOCK_MSG))))
            
            {
                isDeadLock = true;
                return isDeadLock;
            }
        }
        
        return isDeadLock;
    }
    
}


Step 4: Define the target object interface with the annotation.

package com.myapp.engine;

import com.myapp.DeadlockRetry;


public interface AccountServicePersistenceDelegate
{
    @DeadlockRetry(maxTries = 10, tryIntervalMillis = 5000)
    abstract Account getAccount(String accountNumber);
}


Step 5: Define the target object implementaion.

package com.myapp.engine;

import com.myapp.dao.AccountDAO;
...

import javax.annotation.Resource;
import org.springframework.stereotype.Repository;

@Repository
public class AccountServicePersistenceDelegateImpl implements AccountServicePersistenceDelegate
{
    
    @Resource(name = "accountDao")
    private AccountDAO accountDAO;
    
    public Account getStatementOfNetAsset(String accountNumber)
    {
        Account account = accountDAO.getAccount(String accountNumber);
        return account;
    }   
}


Step 6: Invoke the target via the proxy.

...
@Component("accountService")
@ThreadSafe
public class AccountServiceImpl implements AccountService
{

    @Resource
    private AccountServicePersistenceDelegate asServicePersistenceDelegate;
    
    private AccountServicePersistenceDelegate proxyAsPersistenceDelegate;

    @PostConstruct
    public void init()
    {
        this.proxyAsPersistenceDelegate = (AccountServicePersistenceDelegate) Proxy
                .newProxyInstance(AccountServicePersistenceDelegate.class.getClassLoader(), new Class<?>[]
                {AccountServicePersistenceDelegate.class}, 
    new DeadlockRetryHandler(asServicePersistenceDelegate));
    }
 
 public void processAccount(String accountNumber) {
     //...
     Account account = proxyAsPersistenceDelegate.getAccount(accountNumber); // invokes the target via the proxy
  //............
 }
}


Other design patterns - real life examples

Labels: ,

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home