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_tovsto...@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
_______________________________________________
jboss-user mailing list
jboss-user@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/jboss-user

Reply via email to