cancel
Showing results for 
Search instead for 
Did you mean: 

Transaction rolled back

hbf
Champ on-the-rise
Champ on-the-rise
Hi,

I need some advice from a Spring transaction guru, it seems: I don't get the following to work


    try {
      UserTransaction transaction = transactionService.getNonPropagatingUserTransaction();
      transaction.begin();
      authenticationService.authenticate(LOGIN, PASSWORD.toCharArray());
      NodeRef nodeRef = new NodeRef("workspace://SpacesStore/rubbish"); // invalid input
      int status = alfrescoContext.getTransaction().getStatus(); // 0
      nodeService.getProperty(nodeRef, ContentModel.PROP_CATEGORIES);
    }
    catch (Exception e)
    {
      int status = alfrescoContext.getTransaction().getStatus(); // (*)
    }
    transaction.commit();

This code is executed in a seperate Tomcat webapp running alongside the Alfresco Web Client webapp.

With a valid node-ref, the code works perfectly. With the invalid node-ref (as above), getProperty() throws an InvalidNodeRefException exception and already at (*), the transaction's status is 1= STATUS_MARKED_ROLLBACK. Spring is probably intercepting, right?

My question: Why does this mark the transaction for rollback? Is it my duty to make sure the node-ref is correct? I would have thought that if I caught the exception, I can fix the problem and try again (for which, of course, I need a transaction that is not yet marked for rollback)?

Regards,
Kaspar
11 REPLIES 11

hbf
Champ on-the-rise
Champ on-the-rise
The second thing is that you need to be 100% sure that the transactions are not leaked out of scope.  There are three ways to do this.
1. Fix up the try-catch-finally behaviour
2. (Recommended) Use the RetryingTransactionHelper available from TransactionService.  It doesn't leak transactions!
3. Switch on debug to detect any leaked transactions: log4j.logger.org.alfresco.util.transaction.SpringAwareUserTransaction.trace=DEBUG

Hi Derek,

Many thanks for looking into this, very much appreciated. With your explanations, I now understand how this strange behaviour - error from one request "persisted" in the next request - could occur:

In your case, the unclosed transaction is going back into the thread pool. The next operation that attempts to start a new transaction on that thread will generate the "already marked for rollback" exception. That's exactly right. The transaction is still open, you're asking to continue doing some work on the transaction but the transaction is guaranteed to rollback.

Regarding the three ways you mention: I understand that you recommend RetryingTransactionHelper and I am actually using it in our Web layer (which is done in Wicket). However, it seems to be against the normal way in which one would handle an update request: In my web app, I'd like to open a transaction at the beginning of the request, then the MVC code of the web app (scattered over many Wicket components) would make calls to the Alfresco NodeService API and finally a commit() call would save the changes. With RetryingTransactionHelper, however, I cannot (?) do this but am instead forced to collect all calls to the NodeService API in one method, which does not seem natural. Do you have any recommendation for a situation like this?

Thanks for your help,
Kaspar

derek
Star Contributor
Star Contributor

Regarding the three ways you mention: I understand that you recommend RetryingTransactionHelper and I am actually using it in our Web layer (which is done in Wicket). However, it seems to be against the normal way in which one would handle an update request: In my web app, I'd like to open a transaction at the beginning of the request, then the MVC code of the web app (scattered over many Wicket components) would make calls to the Alfresco NodeService API and finally a commit() call would save the changes. With RetryingTransactionHelper, however, I cannot (?) do this but am instead forced to collect all calls to the NodeService API in one method, which does not seem natural. Do you have any recommendation for a situation like this?

Thanks for your help,
Kaspar

Hi, Kaspar
You don't have to use the retrying, of course.  Normally there is a single entry point to the service call where the transaction can be declared, but it's not always convenient or even possible; we have some bits where the transaction start and transaction commit are separated e.g. JCR and CIFS implementations.  The critical thing, though, is not to leak the transaction under any circumstances.  When your commit/rollback code is possibly not going to get called, you should get very nervous; if you're not using RetryingTransactionHelper then make sure your QA includes running with the transaction leak detection enabled.  Aside from all those caveats, you need to do something like this:

   Transaction txn = txnService.getUserTranscation(…);
   try
   {
      txn.begin();
      // Do stuff
      txn.commit();
   }
   catch (Throwable e)
   {
      // Try a rollback.  This doesn't always work if the txn is already marked for rollback.  But who cares as long as it is rolled back!
      try
      {
         txn.rollback();
      }
      catch (Throwable e)
      {
         // Just log, don't rethrow
      }
      // Rethrow original exception
      throw e;
   }
Now, naturally, the outer try-catch can be virtual or implemented by some other mechanism.  But either commit OR rollback must be called and the rollback should be wrapped in a try-catch to absorb any exception (illegal state, etc) that might hide the underlying, original exception that triggered the rollback in the first place.