AnsweredAssumed Answered

Nested transactions and failure of services

Question asked by amenel on Jun 15, 2009
Latest reply on Jun 20, 2009 by mrogers
Hi all,
I have been facing a problem with transactions at the worst moment: everything was fine but falls apart just days before the final delivery of a project based on Alfresco.

Exact details would be too long so I'll just give a general picture. We are storing data into an Alfresco repository through a business-specific front end.To maintain some coherence, all storing operations must be done in one transaction. So we use RetryingTransactionHelper, providing the appropriate callback. However, we want all objects of our content types to be stored in folders whose names are given by the content types. So, to create an object, we must create the right folder for it and put the object under that folder. Which means that if several objects of the same type are created in the one UserTransaction, we will check that the folder exists and retrieve its nodeRef and, if it doesn't, we create it first. Locations are managed by a shared library I am reusing. I can't rewrite it to bypass folder creation.

The first problem is essentially this: any attempt to retrieve the folder fails if the attempt happens in the same not-committed-yet transaction in which the folder was created. So my thought is that surrounding the creation by a nested transaction will solve the problem. Which was not the case: that second UserTransaction may get marked for rollback and the mark stays for the outer transaction… which made me think that transactions are not managed in a stack-like way.

After litterally days of hair-pulling, I realized that the "rename" function of "FileFolderService" was responsible for marking the transaction for roolback when it throws a FileExistsException… which was not noted in the source code.

I ended up surrounding the call to "rename" with a transaction, thinking that I could stack transactions:

         UserTransaction txn;
         txn = serviceRegistry.getTransactionService().getUserTransaction();
         txn.begin();
         try {
            serviceRegistry.getFileFolderService()
                  .rename(newNode, nodeName);
         } catch (FileExistsException e) {
            logger.debug("Failed to rename", e);
         } finally {
            if (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
               txn.rollback();
            } else {
               txn.commit();
            }
         }
      }

Suppose I need to create a "Customer" object with two "Invoice" objects in a global transaction T1.
When I create the first invoice, the nested transaction T2 is commited as expected. For the second invoice, "rename" throws a "FileExistsException" and the transaction (T3 had a "begin()") gets marked for rollback. After rollbacking T3, I could see that the watch expression
serviceRegistry.getTransactionService().getUserTransaction(false).getStatus()
under Eclipse changes from 6 (STATUS_NO_TRANSACTION) to 1 (STATUS_MARKED_ROLLBACK). From that point, the expression remains at 1 for the rest of the time, including for T4 when creating the customer. At the end, T1 never gets the commit I am expecting.

My background is databases and I was expecting many things:
- nested transactions to behave independently from the outer transaction
- transaction set a bookmark in an operations journal and commit/rollback take effect from/back to that bookmark
- the global rollback or commit decision is mine, not the system's. I don't want T1 to fail just because a folder already exists!
- commit or rollback on the inner transaction will (sort of) pop that transaction from the transaction stack.

How do I solve this ? What's the right procedure for having inner transactions ?
Can a single thread have nested transactions ? The SpringAwareUserTransaction javadoc says so but apparently, unless I am doing something wrong, the answer in practice is "no".
I really need help here. Thanks to generous souls for any sort of tip or guidance.

Outcomes