1 2 3 4 Previous Next 49 Replies Latest reply on Jan 21, 2010 10:04 AM by galder.zamarreno Go to original post
      • 30. Re: Transactions: atomicity OK, isolation KO
        jason.greene

        To use the dommy transaction manager in the easiest possible way. Just do getRuntimeConfig().setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");

        Then instead of using UserTransaction, you can just use JTA directly:

        tm = getRuntimeCoinfig.getTransactionManager();
        tm.begin();
        // blah
        tm.commit();

        • 31. Re: Transactions: atomicity OK, isolation KO

          Thanks, i didn't think of using a TM instead of a UserTransaction.
          Using that, i get exactly the same behaviour as with Atomikos though:
          -with pessimistic locking scheme, the test passes
          -with mvcc, i get the same error:

          Detected write skew on Fqn [/foo/mynode]. Another process has changed the node since we last read it!


          • 32. Re: Transactions: atomicity OK, isolation KO
            jason.greene

            That's strange. You should not be getting write-skew, since you never read before you acquire a lock on the node. What happens if you disable write-skew detection?

            • 33. Re: Transactions: atomicity OK, isolation KO

              If i comment the write-skew check, i also get the same behaviour as i did with Atomikos: no exception, but the test fails with

              java.lang.AssertionError: expected:<10> but was:<2>
              

              That is, it fails exactly the same way as if my threads were writing concurrently to a non-thread-safe object.

              • 34. Re: Transactions: atomicity OK, isolation KO

                Good news! It works at last, both with the default TM and with Atomikos.
                To make it work, i used the conf:

                <locking
                 isolationLevel="READ_COMMITTED"
                 lockParentForChildInsertRemove="false"
                 lockAcquisitionTimeout="20000"
                 nodeLockingScheme="mvcc"
                 writeSkewCheck="false"
                 concurrencyLevel="500"/>


                Whereas the not working test had this block commented.

                What's strange is that i had used the same conf a while ago and it failed, but at the time i was using the old configuration file format (with <mbean tags>). It is not the first time i had a bug that was corrected by using the new configuration file.

                Since this took so long, i'm going to post the complete test and configuration in the default and Atomikos cases.

                • 35. Re: Transactions: atomicity OK, isolation KO

                  With JBC's default TM

                  The test:

                  public class ComportementTransactionnelTableCoreCache
                  {
                   private Cache cache;
                   protected TracksTable table = new TracksTableImpl ();
                   protected final ExecutorService executorService = Executors.newCachedThreadPool();
                  
                   @Test
                   public void isolation_RW_RW () throws Exception
                   {
                   table.initScalar ();
                  
                   int nbWriters = 10;
                  
                   CountDownLatch latch = new CountDownLatch ( nbWriters );
                   for ( int i = 0 ; i < nbWriters ; i ++ )
                   {
                   executorService.submit ( new AsyncIncrement ( latch ) );
                   }
                   latch.await ();
                  
                   assertEquals ( nbWriters , table.getScalar() );
                   }
                  
                   protected class AsyncIncrement implements Runnable
                   {
                   private final CountDownLatch latch;
                  
                   public AsyncIncrement ( CountDownLatch latch )
                   {
                   this.latch = latch;
                   }
                  
                   @Override
                   public void run()
                   {
                   try { table.incrementScalar (); }
                   finally { latch.countDown (); }
                   }
                   }
                  
                   @Before
                   public void before ()
                   {
                   cache = initPojoCache ();
                   cache.start();
                   table.setCache ( cache );
                  
                   table.initScalar();
                   assertEquals ( 0 , table.getScalar() );
                   }
                  
                   @After
                   public void after ()
                   {
                   table.unsetCache ();
                   cache.stop();
                   }
                  
                   private Cache initPojoCache()
                   {
                   CacheFactory factory = new DefaultCacheFactory();
                   Cache cache = factory.createCache("resources/META-INF/replSync-service.xml", false);
                   //cache.getConfiguration().setWriteSkewCheck(true);
                  
                   cache.create();
                   return cache;
                   }
                  }
                  


                  The resource under test:
                  public class TracksTableImpl implements TracksTable
                  {
                   private Cache cache;
                   private int scalar;
                  
                   @Override
                   public void incrementScalar()
                   {
                   try
                   {
                   TransactionManager tx = getTm ();
                   tx.begin();
                  
                   cache.put("/foo/mynode", "_lockthisplease_", "_lockthisplease_");
                   cache.getInvocationContext().getOptionOverrides().setForceWriteLock(true);
                   int tmp = (Integer)cache.get("/foo/mynode", "scalar");
                   tmp++;
                   cache.put ( "/foo/mynode" , "scalar" , tmp );
                  
                   tx.commit();
                   }
                   catch ( Exception e )
                   {
                   e.printStackTrace();
                   throw new RuntimeException ();
                   }
                   }
                  
                   @Override
                   public int getScalar()
                   {
                   try
                   {
                   TransactionManager tx = getTm ();
                   tx.begin();
                   int ret = (Integer)cache.get("/foo/mynode", "scalar");
                   tx.commit();
                   return ret;
                   }
                   catch ( Exception e )
                   {
                   e.printStackTrace();
                   throw new RuntimeException ();
                   }
                   }
                  
                   @Override
                   public void initScalar()
                   {
                   try
                   {
                   TransactionManager tx = getTm ();
                   tx.begin();
                   cache.put ( "/foo/mynode" , "scalar" , 0 );
                   tx.commit();
                   }
                   catch ( Exception e )
                   {
                   e.printStackTrace();
                   throw new RuntimeException ();
                   }
                   }
                  
                   @Override
                   public void setCache ( Cache cache )
                   {
                   this.cache = cache;
                   }
                  
                   @Override
                   public void unsetCache ()
                   {
                   this.cache = null;
                   }
                  
                   private TransactionManager getTm()
                   {
                   return cache.getConfiguration().getRuntimeConfig().getTransactionManager();
                   }
                  }


                  The config:
                  <?xml version="1.0" encoding="UTF-8"?>
                  
                  <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.2">
                  
                   <transaction transactionManagerLookupClass="org.jboss.cache.transaction.DummyTransactionManagerLookup"/>
                  
                   <!--
                   isolation levels supported: READ_COMMITTED and REPEATABLE_READ
                   nodeLockingSchemes: mvcc, pessimistic (deprecated), optimistic (deprecated)
                   -->
                   <locking
                   isolationLevel="READ_COMMITTED"
                   lockParentForChildInsertRemove="false"
                   lockAcquisitionTimeout="20000"
                   nodeLockingScheme="mvcc"
                   writeSkewCheck="false"
                   concurrencyLevel="500"/>
                  
                   <!-- By not specifying the 'clustering' element, the cache runs in LOCAL mode. -->
                  
                  </jbosscache>
                  


                  • 36. Re: Transactions: atomicity OK, isolation KO

                    With Atomikos

                    The test:

                    public class ComportementTransactionnelTableCoreCache
                    {
                     private Cache cache;
                     protected TracksTable table = new TracksTableImpl ();
                     protected final ExecutorService executorService = Executors.newCachedThreadPool();
                    
                     @Test
                     public void isolation_RW_RW () throws Exception
                     {
                     table.initScalar ();
                    
                     int nbWriters = 10;
                    
                     CountDownLatch latch = new CountDownLatch ( nbWriters );
                     for ( int i = 0 ; i < nbWriters ; i ++ )
                     {
                     executorService.submit ( new AsyncIncrement ( latch ) );
                     }
                     latch.await ();
                    
                     assertEquals ( nbWriters , table.getScalar() );
                     }
                    
                     protected class AsyncIncrement implements Runnable
                     {
                     private final CountDownLatch latch;
                    
                     public AsyncIncrement ( CountDownLatch latch )
                     {
                     this.latch = latch;
                     }
                    
                     @Override
                     public void run()
                     {
                     try { table.incrementScalar (); }
                     finally { latch.countDown (); }
                     }
                     }
                    
                     @Before
                     public void before ()
                     {
                     cache = initPojoCache ();
                     cache.start();
                     table.setCache ( cache );
                    
                     table.initScalar();
                     assertEquals ( 0 , table.getScalar() );
                     }
                    
                     @After
                     public void after ()
                     {
                     table.unsetCache ();
                     cache.stop();
                     }
                    
                     private Cache initPojoCache()
                     {
                     CacheFactory factory = new DefaultCacheFactory();
                     Cache cache = factory.createCache("resources/META-INF/replSync-service.xml", false);
                    
                     TransactionManager tm = new com.atomikos.icatch.jta.UserTransactionManager ();
                     cache.getConfiguration().getRuntimeConfig().setTransactionManager(tm);
                    
                     cache.create();
                     return cache;
                     }
                    }
                    
                    


                    The resource under test:
                    public class TracksTableImpl implements TracksTable
                    {
                     private Cache cache;
                     private int scalar;
                    
                     @Override
                     public void incrementScalar()
                     {
                     try
                     {
                     UserTransaction tx = getUt ();
                     tx.begin();
                    
                     cache.put("/foo/mynode", "_lockthisplease_", "_lockthisplease_");
                     cache.getInvocationContext().getOptionOverrides().setForceWriteLock(true);
                     int tmp = (Integer)cache.get("/foo/mynode", "scalar");
                     tmp++;
                     cache.put ( "/foo/mynode" , "scalar" , tmp );
                    
                     tx.commit();
                     }
                     catch ( Exception e )
                     {
                     e.printStackTrace();
                     throw new RuntimeException ();
                     }
                     }
                    
                     @Override
                     public int getScalar()
                     {
                     try
                     {
                     UserTransaction tx = getUt ();
                     tx.begin();
                     int ret = (Integer)cache.get("/foo/mynode", "scalar");
                     tx.commit();
                     return ret;
                     }
                     catch ( Exception e )
                     {
                     e.printStackTrace();
                     throw new RuntimeException ();
                     }
                     }
                    
                     @Override
                     public void initScalar()
                     {
                     try
                     {
                     UserTransaction tx = getUt ();
                     tx.begin();
                     cache.put ( "/foo/mynode" , "scalar" , 0 );
                     tx.commit();
                     }
                     catch ( Exception e )
                     {
                     e.printStackTrace();
                     throw new RuntimeException ();
                     }
                     }
                    
                     @Override
                     public void setCache ( Cache cache )
                     {
                     this.cache = cache;
                     }
                    
                     @Override
                     public void unsetCache ()
                     {
                     this.cache = null;
                     }
                    
                     private UserTransaction getUt()
                     {
                     return new com.atomikos.icatch.jta.UserTransactionImp();
                     }
                    }
                    


                    The config:
                    <?xml version="1.0" encoding="UTF-8"?>
                    
                    <jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.2">
                    
                     <!--
                     isolation levels supported: READ_COMMITTED and REPEATABLE_READ
                     nodeLockingSchemes: mvcc, pessimistic (deprecated), optimistic (deprecated)
                     -->
                     <locking
                     isolationLevel="READ_COMMITTED"
                     lockParentForChildInsertRemove="false"
                     lockAcquisitionTimeout="20000"
                     nodeLockingScheme="mvcc"
                     writeSkewCheck="false"
                     concurrencyLevel="500"/>
                    
                     <!-- By not specifying the 'clustering' element, the cache runs in LOCAL mode. -->
                    
                    </jbosscache>
                    


                    • 37. Re: Transactions: atomicity OK, isolation KO

                      With REPEATABLE_READ (the default), it still fails in the way i posted earlier:
                      -if writeSkewCheck is true, i get a write skew exception
                      -otherwise, no exceptions but the behaviour is that of threads writing to a non-thread-safe object

                      • 38. Re: Transactions: atomicity OK, isolation KO

                        Also, would it be possible to have a cache setup parameter that does the same as

                        cache.getCache().getInvocationContext().getOptionOverrides().setForceWriteLock(true);
                        without needing to call it in every service method? Sure it isn't always necessary but in first approximation this would be helpful (optimize later).

                        • 39. Re: Transactions: atomicity OK, isolation KO
                          galder.zamarreno

                          chtimi2, i'm looking into the test you provided

                          • 40. Re: Transactions: atomicity OK, isolation KO
                            galder.zamarreno

                             

                            "chtimi2" wrote:
                            Also, would it be possible to have a cache setup parameter that does the same as
                            cache.getCache().getInvocationContext().getOptionOverrides().setForceWriteLock(true);
                            without needing to call it in every service method? Sure it isn't always necessary but in first approximation this would be helpful (optimize later).


                            You can always provide an abstraction such as getWithWL(Fqn, Object) that does it for you :)

                            • 41. Re: Transactions: atomicity OK, isolation KO
                              galder.zamarreno

                               

                              "chtimi2" wrote:
                              With REPEATABLE_READ (the default), it still fails in the way i posted earlier:
                              -if writeSkewCheck is true, i get a write skew exception
                              -otherwise, no exceptions but the behaviour is that of threads writing to a non-thread-safe object


                              I've just run your test with the dummy cache manager, and I randomly get a:

                              org.jboss.cache.optimistic.DataVersioningException: Detected write skew on Fqn [/foo/mynode]. Another process has changed the node since we last read it!
                               at org.jboss.cache.mvcc.RepeatableReadNode.markForUpdate(RepeatableReadNode.java:68)
                              ...


                              I'm looking into that.

                              Wrt the behaivour when writeSkewCheck is false, could you please enhance your test so that you can assert it in such way that it fails under this condition?

                              • 42. Re: Transactions: atomicity OK, isolation KO
                                galder.zamarreno

                                chtimi2, please find a better version of the test in http://pastebin.mozilla.org/681497

                                It uses correct transaction usage patterns as instructed http://www.jboss.org/community/wiki/WhatIsTheCorrectPatternForUserTransactions, uses Callable and Future to propagate exceptions back to the main thread, and finally uses CyclicBarrier to make sure all threads start at the same time and they wait until everyone has finished.

                                I'm still investigating the write skew true issue.

                                • 43. Re: Transactions: atomicity OK, isolation KO
                                  galder.zamarreno

                                  Here's a bug report for the writeSkew issuel: https://jira.jboss.org/jira/browse/JBCACHE-1555

                                  • 44. Re: Transactions: atomicity OK, isolation KO

                                    Thanks for investigating the issue. I have a lot of slides to write so it might take a week before i can dig more but after that I will try your test, promised :)