[jboss-user] [JBoss Cache: Core Edition] - Re: is it possible for MVCCInvocationContext to have null mv

dukehoops do-not-reply at jboss.com
Thu Mar 12 14:29:34 EDT 2009


Manik,

Your explanation makes sense, however I wonder whether that means Hibernate / Hib-JBC2 integration project / JBC3 stack is unusable with REPEATABLE_READ then. Let me explain.

Person, Child are cached entities, parent.children is a cached collection (using Hibernate's Transactional concurrency strategy).

Consider this method:


  | public void addChild(parentId){
  | 
  | tx.begin();
  | 
  | Parent p = parentDAO.get(parentId);
  | Set<Child> children = p.getChildren();
  | Child newChild = new Child();
  | children.add(newChild);
  | tx.commit();
  | }
  | 

Let's look at what calls hib-jbc integration layer (org.hibernate.cache.jbc2.[collection|entity].TransactionalAccess) will issue for each of the above lines:

1. Parent p = parentDAO.get(parentId);

cache.get(parentIdFqn); //cache miss on very 1st call
//hibernate goes to db to retrieve data and then puts it into cache:
cache.pfer(parentIdFqn, p)

2. Set children = p.getChildren();

//look in collection cache for parent.children with given parentId
cache.get(parentIdCollectionFqn) //miss
//hibernate will look in db, and if db returns nothing, will put an empty collection into cache
cache.pfer(parentIdCollectionFqn, emptyCollection);

3. children.add(newChild);

Hibernate tells 2nd level cache to evict collection cache entry on any collection modification. Specifically above add call will result in removeNode call on tx.commit():

cache.removeNode(parentIdCollectionFqn); //should remove empty Collection from cache to ensure next time step #2 above is executed cache misses and we go to DB.

However, as you point out pfer() will operate in different tx than get() and removeNode(). Therefore, removeNode will not remove the empty collection. Now Node with parentIdCollectionFqn still has emptyCollection as value and so in a subsequent transaction:

tx.begin();
cache.get(parentIdCollectionFqn); //hibernate expects a miss so that new children can be re-read from db
tx.commit();
.. we will actually get a cache hit with stale emptyCollection, one that does not contain newChildId. So as far as Hibernate will be concerned, given parent will have no children. (even though newChild is in db).

So, I understand your explanation and agree that JBC code behaves correctly. But it seems like the problem is that when child is added to a collection in Hibernate, collection cache will see exactly this series of calls:

tx1:
-get()
-pfer()
-removeNode() //expected to ensure cache miss on the subsequent get()

tx2:
-get() //expect a miss 


So that means that org.hibernate.cache.jbc2.collection.TransactionalAccess.putFromLoad() should use a Cache method that ensures that a subsequent Cache.removeNode will actually remove, i.e putFromLoad()
- either should use put() instead of pfer()
- or use a new method that is like pfer() but does not suspend TX.

Here's the updated test that I believe issues the same calls that org.hibernate.cache.jbc2.collection.TransactionalAccess would issue with the above service method:


package org.jboss.cache.api;
  | 
  | import org.jboss.cache.config.Configuration;
  | import org.jboss.cache.config.Configuration.NodeLockingScheme;
  | import org.jboss.cache.factories.UnitTestConfigurationFactory;
  | import org.testng.annotations.Test;
  | 
  | import javax.transaction.TransactionManager;
  | 
  | import org.jboss.cache.*;
  | import org.jboss.cache.lock.IsolationLevel;
  | 
  | /**
  |  * Tests that a node that was putFromExternalRead and then removed in TX1 does NOT
  |  * get returned in subsequent TX2.
  |  *
  |  * @author nikita_tovstoles at mba.berkeley.edu
  |  * @since 3.0.3.GA
  |  */
  | @Test(groups = {"functional", "pessimistic"}, sequential = true, testName = "api.RemovedNodeResurrectionInSubsequentTxTest")
  | public class RemovedNodeResurrectionInSubsequentTxTest extends AbstractSingleCacheTest {
  | 
  |     private static final Fqn A_B = Fqn.fromString("/a/b");
  |     private static final Fqn A = Fqn.fromString("/a");
  |     private static final Fqn A_C = Fqn.fromString("/a/c");
  |     private static final String KEY = "key";
  |     private static final String VALUE = "value";
  |     private static final String K2 = "k2";
  |     private static final String V2 = "v2";
  |     protected NodeSPI root;
  |     protected TransactionManager txManager;
  | 
  |     public CacheSPI createCache() {
  |         CacheSPI myCache = (CacheSPI<Object, Object>) new UnitTestCacheFactory<Object, Object>().createCache(UnitTestConfigurationFactory.createConfiguration(Configuration.CacheMode.LOCAL, false), false, getClass());
  |         myCache.getConfiguration().setCacheMode(Configuration.CacheMode.LOCAL);
  |         myCache.getConfiguration().setCacheLoaderConfig(null);
  |         myCache.getConfiguration().setNodeLockingScheme(NodeLockingScheme.MVCC);
  |         configure(myCache.getConfiguration());
  |         myCache.start();
  |         root = myCache.getRoot();
  |         txManager = myCache.getTransactionManager();
  | 
  |         return myCache;
  |     }
  | 
  |     protected void configure(Configuration c) {
  |         c.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
  |     }
  | 
  |     public void testPferAndEvictInSameTx() throws Exception {
  | 
  |         Object val = null;
  | 
  |         cache.getRoot().addChild(A);
  | 
  |         txManager.begin();
  | 
  |         val = cache.get(A_B, KEY); //N1 IF THIS LINE IS EXECUTED, TEST FAILS BELOW AT N2
  | 
  |         //put in cache
  |         cache.putForExternalRead(A_B, KEY, VALUE);
  | 
  |         //val = cache.get(A_B, KEY);
  |         //assert val != null : "get() after pfer() returned " + val;  //N2 THIS WILL FAIL IF LINE N1 (ABOVE) IS EXECUTED (NOT COMMENTED OUT)
  | 
  |         //evict from cache
  |         cache.removeNode(A_B); //sometimes MVCCInvocationContext has ref to mvccTCtx, sometimes NOT
  | 
  |         //verify eviction
  |         //val = cache.get(A_B, KEY);
  |         //assert val == null : "get() after evict() returned " + val;
  | 
  |         txManager.commit();
  | 
  |         txManager.begin();
  |         //verify miss
  |         val = cache.get(A_B, KEY);
  |         assert val == null : "get() after tx.commit returned " + val;
  | 
  |         txManager.commit();
  | 
  |     }
  | }
  | 

and corresponding failure expected from JBC core perspective but NOT from Hibernate perspective:
anonymous wrote : java.lang.AssertionError: get() after tx.commit returned value
  | 	at org.jboss.cache.api.RemovedNodeResurrectionInSubsequentTxTest.testPferAndEvictInSameTx(RemovedNodeResurrectionInSubsequentTxTest.java:78)
  | 	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
  | 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
  | 	at java.lang.Thread.run(Thread.java:619)
  | 

View the original post : http://www.jboss.org/index.html?module=bb&op=viewtopic&p=4217616#4217616

Reply to the post : http://www.jboss.org/index.html?module=bb&op=posting&mode=reply&p=4217616



More information about the jboss-user mailing list