5 Replies Latest reply on Sep 19, 2018 5:34 AM by suenda

    Rollback a transaction after a runtime exception during commit

    suenda

      I use transaction to update several caches atomically. However, recently I have found that a runtime exception thrown during the commit phase does not rollback the transaction. A HeuristicMixedException is thrown by the transactionManager and a part of the transaction is commited.

       

      I was able to reproduce the scenario in a test :

      @Test
      public void testRollBack() {
      
          ConfigurationBuilder builder = new ConfigurationBuilder();
          builder
                  .clustering().cacheMode(CacheMode.DIST_SYNC)
                  .transaction()
                  .transactionMode(TransactionMode.TRANSACTIONAL)
                  .transactionManagerLookup(new JBossStandaloneJTAManagerLookup()).lockingMode(LockingMode.OPTIMISTIC)
                  .persistence().addStore(MyCacheStoreConfigurationBuilder.class)
                  .fetchPersistentState(true)
                  ;
          Configuration conf = builder.build();
      
          GlobalConfiguration gcb = new GlobalConfigurationBuilder()
                  .transport()
                  .defaultTransport()
                  .clusterName("TEST_CLUSTER")
                  .defaultCacheName("default").build();
      
          DefaultCacheManager cacheManager = new DefaultCacheManager(gcb, conf);
      
          Cache<idrevision, article=""> reference = cacheManager.getCache("reference");
          Cache<uuid, long=""> latestHit = cacheManager.getCache("hit");
      
          TransactionManager transactionManager = reference.getAdvancedCache().getTransactionManager();
      
          try {
              transactionManager.begin();
          } catch (NotSupportedException | SystemException e) {
              System.err.println(e.getClass().getName() + ": " + e.getMessage());
              return;
          }
      
          Article article = new Article();
      
          article.setId(UUID.randomUUID());
          article.setRevision(1L);
          article.setContent("Content");
      
          try {
              latestHit.put(article.getId(), System.currentTimeMillis());
              reference.put(article.getIdRevision(), article);
          } catch (Exception e) {
              try {
                  transactionManager.rollback();
              } catch (IllegalStateException | SecurityException | SystemException e1) {
                  e1.printStackTrace();
                  return;
              }
          }
          try {
      
              // The CacheStore MyCacheStore throws a IllegalStateException while
              // writing the Article object.
              transactionManager.commit();
      
          } catch (SecurityException | IllegalStateException | RollbackException | HeuristicRollbackException
                  | SystemException e) {
              e.printStackTrace();
          } catch (HeuristicMixedException e) {
              // the exception thrown by TransactionManager
              e.printStackTrace();
          }
      
          Long hit = latestHit.get(article.getId());
          assertThat("Rollback failed", hit, nullValue());
      }

      The test above fails and I see that one of the caches is updated while the other is not. Is there any way to rollback the transaction?

       

      I am using Infinispan 9.0.1.Final and narayana-jta 5.0.3.Final as the Transaction Manager.

       

      Please see the zip attachment for the test project for code. To run my test, type : mvn clean test -Dtest=TestTransaction

        • 1. Re: Rollback a transaction after a runtime exception during commit
          rvansa

          Could you post details about the cause for the rollback? (What is the chain of e.getCause() for the HeuristicMixedException - there are likely multiple ones).

           

          Transaction commit has two phases:

          1. all modified keys are locked and we verify that read version matches (check out 'write skew' in our docs)

          2. changes to all modified entries are updated

           

          Second phase should not exhibit any 'failures' as some entries can be already updated. In practice errors can happen (e.g. due to network instability), but all you can do at that point is just tell the user: the transaction won't be committed atomically. The user can then try to 'fix things up', but there may be an inconsistency. Checkout ConflictManager) .

           

          My guess is that your custom cache store is failing in the second phase - you could implement TransactionalCacheWriter and make sure that you will be able to do commit cleanly in the prepareWithModifications method.

          • 2. Re: Rollback a transaction after a runtime exception during commit
            suenda

            Thank you for your reply.

             

            The cause of the exception in the stacktrace is org.infinispan.commons.CacheException which is caused by a RuntimeException in the CacheWriter implementation.

             

            Indeed, the transaction failed in the second phase. I will definitely look at TransactionalCacheWriter and will let you know.

            • 3. Re: Rollback a transaction after a runtime exception during commit
              suenda

              rvansa

              I tried implementing TransactionalCacheWriter in my test context and I looked into the implementation of JdbcStringBasedStore which implements TransactionalCacheWriter for inspiration. It does not seem to resolve my problem and I still see only one of the caches being updated.

               

              I have uploaded an zip archive of my test project. To launch my test in maven, type : mvn test -Dtest=TestTransaction.

               

              I would be very grateful if you could look into my implementation of TransactionalCacheWriter or for any pointers to approach this problem.

              • 4. Re: Rollback a transaction after a runtime exception during commit
                rvansa

                Well if you say your implementation throws RuntimeException in second phase, the answer is simple: don't make it throw that exception. By implementing the TransactionalCacheWriter you know that you're going to fail ahead - check all preconditions (in your case the value type) ahead in the prepareWithModifications() and let commit be smooth as silk.

                • 5. Re: Rollback a transaction after a runtime exception during commit
                  suenda

                  Checking for all possible exception in the prepareWithModification() and throwing a PersistenceException effectively solves my problem in hand.

                   

                  However, what if it my cachestore fails in the commit() phase (let's say beceause of IOException or some other runtime issues)? I see that the transaction is partially committed. Is there any way around this?

                   

                  Your pointers have been really constructive and I thank you for your help.