Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PlayFramework: catch a deadlock and reissue transaction

I am running a Play! application and am debugging a deadlock.

The error messages I see logged from Play! are:
Deadlock found when trying to get lock; try restarting transaction
Could not synchronize database state with session
org.hibernate.exception.LockAcquisitionException: Could not execute JDBC batch update

From the Play! Documentation

Play will automatically manage transactions for you. It will start a transaction for each HTTP request and commit it when the HTTP response is sent. If your code throws an exception, the transaction will automatically rollback.

From the MySQL Documentation

you must write your applications so that they are always prepared to re-issue a transaction if it gets rolled back because of a deadlock.

My question:
How and where in my Play! application can I catch these rolled back transactions, and handle them (choose to reissue them, ignore them, etc...)?

Updated
While I ended up taking the advice in the accepted answer, and looking for the cause of the deadlock (did you know MySQL foreign key constraints increase the chance of deadlocking? Now I do!), here is some code that was working for me to catch and reissue a failed save.

boolean success = false;
int tries = 0;
while (!success && tries++ < 3) {
    try {
        updated.save();
        success = true;
    } catch (javax.persistence.PersistenceException e) {
        pause(250);  
    }
}
like image 303
goggin13 Avatar asked Dec 05 '25 01:12

goggin13


2 Answers

You can use custom Enhancer and enhance all controllers in your plugin.

For example, enhancer add catch block and restart request invocation. It is more reliable for restart request then only part of logic inside controller:

package plugins;
..
final public class ReliableTxPlugin extends PlayPlugin {
  public void enhance(final ApplicationClasses.ApplicationClass applicationClass) throws Exception {
    new TxEnhancer().enhanceThisClass(applicationClass);
  }
}



package enhancers;
..
class TxEnhancer extends Enhancer {

  public static void process(PersistenceException e) throws PersistenceException {
    final Throwable cause = e.getCause();
    if (cause instanceof OptimisticLockException || cause instanceof StaleStateException) {
      final EntityTransaction tx = JPA.em().getTransaction();
      if (tx.isActive()) {
        tx.setRollbackOnly();
      }
      Http.Request.current().isNew = false;
      throw new Invoker.Suspend(250);
    }
    throw e;
  }    
  public void enhanceThisClass(final ApplicationClass applicationClass) throws Exception {
    // .. general encahcer code   
    ctMethod.addCatch("enhancers.TxEnhancer.process(_e);return;",
    classPool.makeClass("javax.persistence.PersistenceException"), "_e");
    //..
  }  
}
like image 83
xeodon Avatar answered Dec 07 '25 15:12

xeodon


In most cases of a deadlock you can only do a rollback and try a restart. How ever in a Webapp this should be a really unusual case. What you can do in Play is

  • catch the exception
  • handle the Transaction in your code

With JPA.em() you will get the EntityManager. You can look into JPAPlugin to see how play handle the transaction. How ever first of all I would evaluate why there is a deadlock and if this is really a realistic situation which can the server handle intelligent.

like image 24
niels Avatar answered Dec 07 '25 15:12

niels



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!