1 2 Previous Next 15 Replies Latest reply on Jul 14, 2006 4:52 AM by Wim De Muynck

    TreeCache/Hibernate/JBossAS optimistic locking problem

    Wim De Muynck Newbie

      I get the error below (at CMT commit time) when using the non-clustered combo TreeCache 1.4.0cr2 / Hibernate 3.2.0cr2 / JBoss 4.0.4GA. When I switch to pessimistic locking (replace OptimisticTreeCacheProvider with TreeCacheProvider and NodeLockingScheme OPTIMISTIC by PESSIMISTIC), it all works without faults.

      While debugging through the JBoss code, it seems that realNode.getVersion() at line 124 in OptimisticValidatorInterceptor.simpleValidate() is null.

      Any ideas? Tnx!


      Caused by: java.lang.NullPointerException
      at org.jboss.cache.interceptors.OptimisticValidatorInterceptor.simpleValidate(OptimisticValidatorInterceptor.java:124)
      at org.jboss.cache.interceptors.OptimisticValidatorInterceptor.validateNodes(OptimisticValidatorInterceptor.java:101)
      at org.jboss.cache.interceptors.OptimisticValidatorInterceptor.invoke(OptimisticValidatorInterceptor.java:66)
      at org.jboss.cache.interceptors.Interceptor.invoke(Interceptor.java:68)
      at org.jboss.cache.interceptors.OptimisticLockingInterceptor.invoke(OptimisticLockingInterceptor.java:95)
      at org.jboss.cache.interceptors.Interceptor.invoke(Interceptor.java:68)
      at org.jboss.cache.interceptors.TxInterceptor.runPreparePhase(TxInterceptor.java:796)
      at org.jboss.cache.interceptors.TxInterceptor$LocalSynchronizationHandler.beforeCompletion(TxInterceptor.java:1061)
      ... 73 more

        • 1. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
          Wim De Muynck Newbie

          More news:

          It seems it is happening with the query cache enabled, since at the location of the exception the toString() of workspaceNode is:

          WorkNode fqn=/org/hibernate/cache/StandardQueryCache dirty ver=null

          When I disable the query cache, I get another similar exception:

          Caused by: java.lang.NullPointerException
          at org.hibernate.util.ComparableComparator.compare(ComparableComparator.java:13)
          at org.hibernate.cache.OptimisticTreeCache$DataVersionAdapter.newerThan(OptimisticTreeCache.java:258)
          at org.jboss.cache.interceptors.OptimisticValidatorInterceptor.simpleValidate(OptimisticValidatorInterceptor.java:124)
          at org.jboss.cache.interceptors.OptimisticValidatorInterceptor.validateNodes(OptimisticValidatorInterceptor.java:101)
          at org.jboss.cache.interceptors.OptimisticValidatorInterceptor.invoke(OptimisticValidatorInterceptor.java:66)
          at org.jboss.cache.interceptors.Interceptor.invoke(Interceptor.java:68)
          at org.jboss.cache.interceptors.OptimisticLockingInterceptor.invoke(OptimisticLockingInterceptor.java:95)
          at org.jboss.cache.interceptors.Interceptor.invoke(Interceptor.java:68)
          at org.jboss.cache.interceptors.TxInterceptor.runPreparePhase(TxInterceptor.java:796)
          at org.jboss.cache.interceptors.TxInterceptor$LocalSynchronizationHandler.beforeCompletion(TxInterceptor.java:1061)
          ... 73 more

          Now in order to force it to proceed, I tried to patch org.hibernate.util.ComparableComparator.compare()
          by adding if (x == null) return -1;

          This seems to make it work, but I get:
          WARN [OptimisticTreeCache] Unexpected optimistic lock check on inserted data

          It seems that the core of the problem is that org.hibernate.cache.OptimisticTreeCache$DataVersionAdapter.previousVersion is null in some cases, probably set by org.hibernate.cache.OptimisticTreeCache.writeLoad(), but I have no clue as to why these lock checks occur, or whether these instances with a version or previousversion set to null are ok.

          • 2. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
            Steve Ebersole Apprentice

            My guess is that you are seeing a manifestation of http://opensource.atlassian.com/projects/hibernate/browse/HHH-1796

            There is absolutely no way that Hibernate would apply a DataVersion to a *query cache* region, as they logically should have no versioning info at all. Thus we use null DataVersion, which my testing (within the Hibernate test suite) shows is perfectly valid.

            writeLoad() pushing a null into the DataVersionAdapter.previousVersion should be fine...

            • 3. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
              Wim De Muynck Newbie

              Well, no luck. I replaced OptimisticTreeCache with the latest version from CVS, which, as I understood from reading HHH-1796 at least, should fix the problem using a new Fqn.

              I've been debugging and browsing code for hours now trying to figure out what is going on and I think problem is twofold, and both are related. It seems at some point instances of org.jboss.cache.optimistic.WorkspaceNodeImpl, which by default appear to have their version set to DefaultDataVersion.ZERO, are altered via a call setVersion() with null as an argument.

              1/ Now the first part of the problem seems exactly what you mention, i.e. "no way Hibernate would apply a DataVersion to a query cache thus we set null".

              But looking at these lines from
              org.jboss.cache.interceptors.OptimisticCreateIfNotExistsInterceptor.createNode():

              DataVersion version = null;
               if (ctx.getOptionOverrides() != null && ctx.getOptionOverrides().getDataVersion() != null)
               {
               version = ctx.getOptionOverrides().getDataVersion();
               workspace.setVersioningImplicit( false );
               }


              it seems the transaction workspace's versioning mode is set to explicit versioning when we pass in an Option which sets a DataVersion, for example when calling OptimisticTreeCache.writeLoad(). But then, somewhat further in the same method, we have:

              if (!workspace.isVersioningImplicit()) childWorkspaceNode.setVersion(version);


              If now this method gets called again by Hibernate's StandardQueryCache for example, via OptimisticTreeCache.put(), then the workspace versioningImplicit still remains false. Since a put call like this one doesn't contain a set dataversion, the above method call will effectively reset the default DefaultDataVersion.ZERO of org.jboss.cache.optimistic.WorkspaceNodeImpl to null.

              Since I do not have intimate knowledge of the JBoss and Hibernate code, I may be totally wrong, but adding the line below to OptimisticTreeCache.put() sure does fix the problem. This might not be the ideal fix since I assumed only StandardQueryCache directly calls OptimisticTreeCache.put():

              option.setDataVersion(DefaultDataVersion.ZERO);


              2/ The second part of the problem seems similar, but it appears to be caused by a call to writeLoad() where source==null. This also ultimately boils down to passing in an Option with a dataversion null. In the stack this happens when loading a one to many collection. But I'm still trying to find out what is happening there.



              • 4. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                Steve Ebersole Apprentice

                Here is the test to which I refer, which works (from the Hibernate test suite):

                 public void testNoVersioningProvided() throws Throwable {
                 // note : mimic query cache interactions...
                 Fqn fqn = Fqn.fromString( "org/hibernate/querycache" );
                 TreeCache treeCache = ( ( OptimisticTreeCacheProvider ) sfi().getSettings().getCacheProvider() ).getUnderlyingCache();
                
                 Long long1 = new Long(1);
                 Long long2 = new Long(2);
                
                 Option option = new Option();
                 option.setFailSilently( true );
                
                 try {
                 System.out.println( "****** General put... *******************************************" );
                 DummyTransactionManager.INSTANCE.begin();
                 treeCache.put( fqn, "ITEM1", long1, option );
                 DummyTransactionManager.INSTANCE.commit();
                 System.out.println( "****** General put... *******************************************" );
                 DummyTransactionManager.INSTANCE.begin();
                 treeCache.put( fqn, "ITEM1", long2, option );
                 DummyTransactionManager.INSTANCE.commit();
                
                 System.out.println( "****** General put... *******************************************" );
                 DummyTransactionManager.INSTANCE.begin();
                 treeCache.put( fqn, "ITEM2", long1, option );
                 DummyTransactionManager.INSTANCE.commit();
                
                 // try puts outside scope of transaction...
                 System.out.println( "****** No-txn put... *******************************************" );
                 treeCache.put( fqn, "ITEM1", long2, option );
                 System.out.println( "****** No-txn put... *******************************************" );
                 treeCache.put( fqn, "ITEM2", long2, option );
                
                 // actual processing in handling a cached query
                 System.out.println( "****** actual query cache put logic ****************************" );
                 DummyTransactionManager.INSTANCE.begin();
                 if ( treeCache.get( fqn, "ITEM3", option ) == null ) {
                 // run the query, then put...
                 treeCache.put( fqn, "ITEM3", long1, option );
                 }
                 System.out.println( "****** actual query cache put logic ****************************" );
                 if ( treeCache.get( fqn, "ITEM3", option ) == null ) {
                 // run the query, then put...
                 treeCache.put( fqn, "ITEM3", long1, option );
                 }
                 DummyTransactionManager.INSTANCE.commit();
                
                 // actual processing in handling a cached query (no txn)
                 System.out.println( "****** actual query cache put logic ****************************" );
                 if ( treeCache.get( fqn, "ITEM3", option ) == null ) {
                 // run the query, then put...
                 treeCache.put( fqn, "ITEM3", long1, option );
                 }
                 System.out.println( "****** actual query cache put logic ****************************" );
                 if ( treeCache.get( fqn, "ITEM3", option ) == null ) {
                 // run the query, then put...
                 treeCache.put( fqn, "ITEM3", long1, option );
                 }
                 }
                 finally {
                 try {
                 treeCache.remove( fqn, "ITEM1", option );
                 treeCache.remove( fqn, "ITEM2", option );
                 treeCache.remove( fqn, "ITEM3", option );
                 }
                 catch( Throwable ignore ) {
                 }
                 }
                 }
                


                • 5. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                  Steve Ebersole Apprentice

                  Now, Hibernate is currently bundling an older version of TreeCache. Perhaps something changed in newer versions which caused this to start failing

                  • 6. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                    Steve Ebersole Apprentice

                    as for your point #2, the *only* time a call should ever get routed through writeLoad() with a null source is a collection cache. There is a hole in our test suite regarding optimistic caches in conjunction with collection caches because I had not intended to apply optimisitc locking to the collection cache regions. So this may be an issue; I'll add a test and see what happens...

                    • 7. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                      Wim De Muynck Newbie

                      I'll dig into this further tomorrow.

                      But just by quickly looking at the test case you mention, and according to what I saw TreeCache doing today, that test may pass since it only seems to deal with non-versioning stuff within a transaction. But that might not be the case when running for real, since you'll have mixed non-versioning (query cache) calls and versioning (regular versioned data cache) calls within the *same transaction*. But as it seems, as pointed out before, TreeCache's transaction workspace is set to explicit user versioning as soon as you pass in a DataVersion via an Option (e.g. using write, writeLoad, ...). Hence subsequent non-versioning calls seem to be ought versioning calls, which, according to what I saw, fails. So your test may fail once you add a regular versioning call in between there.

                      As for your reply to point 2, yes, then that is likely to be the reason, since once source==null, you effectively pass in an Option with dataversion=null (=non-versioning), which fails in the aforementioned situation, i.e. when mixed in the same transaction with a versioning call.

                      • 8. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                        Wim De Muynck Newbie

                        Clarification to my previous post:
                        As for point 2, yes, I confirm, source==null happened in a case where a cached collection was loading (or being put into; don't remember) from its collection cache.

                        • 9. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                          Wim De Muynck Newbie

                          Just checked out the latest Hibernate source from http://anonhibernate.labs.jboss.com/trunk/Hibernate3, but I can't find a method testNoVersioningProvided() in there ... where should this be included?

                          Anyway, my guess is still remains: TreeCache doesn't work fine if you mix non-versioning and versioning calls in the same transaction. So either one could fix TreeCache such that DefaultDataVersion.ZERO is again correctly assigned to WorkSpaceNodeImpl when using a non-versioning call, either you pass in via OptimisticTreeCache a DefaultDataVersion.ZERO, as I tried.

                          Same way, problem nr 2 can be fixed by adding to writeUpdate() and writeLoad():

                          if ( source != null ) {
                           ...
                           } else
                           option.setDataVersion(DefaultDataVersion.ZERO);


                          and modifying :

                          public boolean newerThan(DataVersion dataVersion) {
                           if ( previousVersion == null ) {
                           log.warn( "Unexpected optimistic lock check on inserted data" );
                           return false;
                           }
                           ...


                          Replacing the null parameter in writeLoad to currentVersion may have the same effect.


                          But I'm still confused by the warning issued by the code above. Why shouldn't there be an optimistic lock check on inserted data? Based on what I read in the TreeCache documentation and from what I saw in the code, everything accessed by a transaction is put into it's workspace (both read/write, it seems). At commit time, everything in the workspace is being checked for newer versions against the cache (see org.jboss.cache.interceptors.OptimisticValidatorInterceptor.simpleValidate()) which, as I understand it, boils down to obtaining both optimistic read and write locks, which is neat and exactly what I want.

                          Being non-familiar with the JBoss code, I may be totally wrong or looking at the wrong root cause, but this is the only explanation IMHO I'm able to see.


                          • 10. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                            Steve Ebersole Apprentice

                            I have some local mosds to these tests...

                            I just confirmed with Manik regarding the mixing of calls supplying and calls not supplying a data-version. Hibernate will need to provided data-version info for all nodes under its control reagrdless of whether optimistically locking these nodes makes any sense...

                            Reagrding the warning, the message is just a bit misleading (and I have actually changed it locally). It is really protecting that *inserting* data is not involved in a lock check (because that would mean that data already existed for the entity we are trying to insert). But I found an interesting thing (which perhaps was the initial thing which caused me to add this): basically, after an insert in one transaction, the node is validated against itself in the next transaction!?!?! I would guess that it has something to do with the fact that tree cache is registering its synch inthe midst of a beforeCompletion() cycle, but not certain. Would anything else cause tree cache to validate a non modified node in this manner?

                            • 11. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                              Steve Ebersole Apprentice

                              So if you could, I can post here the latest changes I have made to the o.h.c.OptimisticTreeCache class for you to verify. It should fix the vast majority of the issue you were seeing. There are still some "transactionality mismatch" (for lack of a better term) issues Hibernate and JBoss Cache need to work through. For Hibernate users this would effect only two areas I am aware of:
                              1) query result caching. This will not work at all with JBoss Cache in optimistic mode. The underlying issue being that Hibernate tries to write some info needed for query result caching into the underlying cache during after-transaction processing which JBoss Cache barfs at. Actually this *will* work as long as the queries are not requested less than .6 seconds after updates to any of the "table spaces" that affect that result...
                              2) an edge case where CMT is utilized relying on Hibernate's flush-on-completion where Hibernate made no calls into the TreeCache prior to the commit/flush and the flush actually needs to write state changes...

                              • 12. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                                Steve Ebersole Apprentice

                                 

                                //$Id: OptimisticTreeCache.java 9965 2006-05-30 13:00:28 -0500 (Tue, 30 May 2006) steve.ebersole@jboss.com $
                                package org.hibernate.cache;
                                
                                import java.util.HashMap;
                                import java.util.Iterator;
                                import java.util.Map;
                                import java.util.Set;
                                import java.util.Comparator;
                                
                                
                                import org.apache.commons.logging.Log;
                                import org.apache.commons.logging.LogFactory;
                                import org.jboss.cache.Fqn;
                                import org.jboss.cache.optimistic.DataVersion;
                                import org.jboss.cache.config.Option;
                                import org.jboss.cache.lock.TimeoutException;
                                
                                /**
                                 * Represents a particular region within the given JBossCache TreeCache
                                 * utilizing TreeCache's optimistic locking capabilities.
                                 *
                                 * @see OptimisticTreeCacheProvider for more details
                                 *
                                 * @author Steve Ebersole
                                 */
                                public class OptimisticTreeCache implements OptimisticCache {
                                
                                 // todo : eventually merge this with TreeCache and just add optional opt-lock support there.
                                
                                 private static final Log log = LogFactory.getLog( OptimisticTreeCache.class);
                                
                                 private static final String ITEM = "item";
                                
                                 private org.jboss.cache.TreeCache cache;
                                 private final String regionName;
                                 private final Fqn regionFqn;
                                 private OptimisticCacheSource source;
                                
                                 public OptimisticTreeCache(org.jboss.cache.TreeCache cache, String regionName)
                                 throws CacheException {
                                 this.cache = cache;
                                 this.regionName = regionName;
                                 this.regionFqn = Fqn.fromString( regionName.replace( '.', '/' ) );
                                 }
                                
                                
                                 // OptimisticCache impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                
                                 public void setSource(OptimisticCacheSource source) {
                                 this.source = source;
                                 }
                                
                                 public void writeInsert(Object key, Object value, Object currentVersion) {
                                 writeUpdate( key, value, currentVersion, null );
                                 }
                                
                                 public void writeUpdate(Object key, Object value, Object currentVersion, Object previousVersion) {
                                 try {
                                 Option option = new Option();
                                 DataVersion dv = ( source != null && source.isVersioned() )
                                 ? new DataVersionAdapter( currentVersion, previousVersion, source.getVersionComparator(), source.toString() )
                                 : NonLockingDataVersion.INSTANCE;
                                 option.setDataVersion( dv );
                                 cache.put( new Fqn( regionFqn, key ), ITEM, value, option );
                                 }
                                 catch ( Exception e ) {
                                 throw new CacheException( e );
                                 }
                                 }
                                
                                 public void writeLoad(Object key, Object value, Object currentVersion) {
                                 try {
                                 Option option = new Option();
                                 option.setFailSilently( true );
                                 option.setDataVersion( NonLockingDataVersion.INSTANCE );
                                 cache.remove( new Fqn( regionFqn, key ), "ITEM", option );
                                
                                 option = new Option();
                                 option.setFailSilently( true );
                                 DataVersion dv = ( source != null && source.isVersioned() )
                                 ? new DataVersionAdapter( currentVersion, currentVersion, source.getVersionComparator(), source.toString() )
                                 : NonLockingDataVersion.INSTANCE;
                                 option.setDataVersion( dv );
                                 cache.put( new Fqn( regionFqn, key ), ITEM, value, option );
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                
                                 // Cache impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                
                                 public Object get(Object key) throws CacheException {
                                 try {
                                 Option option = new Option();
                                 option.setFailSilently( true );
                                // option.setDataVersion( NonLockingDataVersion.INSTANCE );
                                 return cache.get( new Fqn( regionFqn, key ), ITEM, option );
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                 public Object read(Object key) throws CacheException {
                                 try {
                                 return cache.get( new Fqn( regionFqn, key ), ITEM );
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                 public void update(Object key, Object value) throws CacheException {
                                 try {
                                 Option option = new Option();
                                 option.setDataVersion( NonLockingDataVersion.INSTANCE );
                                 cache.put( new Fqn( regionFqn, key ), ITEM, value, option );
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                 public void put(Object key, Object value) throws CacheException {
                                 try {
                                 log.trace( "performing put() into region [" + regionName + "]" );
                                 // do the put outside the scope of the JTA txn
                                 Option option = new Option();
                                 option.setFailSilently( true );
                                 option.setDataVersion( NonLockingDataVersion.INSTANCE );
                                 cache.put( new Fqn( regionFqn, key ), ITEM, value, option );
                                 }
                                 catch (TimeoutException te) {
                                 //ignore!
                                 log.debug("ignoring write lock acquisition failure");
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                 public void remove(Object key) throws CacheException {
                                 try {
                                 // tree cache in optimistic mode seems to have as very difficult
                                 // time with remove calls on non-existent nodes (NPEs)...
                                 if ( cache.get( new Fqn( regionFqn, key ), ITEM ) != null ) {
                                 Option option = new Option();
                                 option.setDataVersion( NonLockingDataVersion.INSTANCE );
                                 cache.remove( new Fqn( regionFqn, key ), option );
                                 }
                                 else {
                                 log.trace( "skipping remove() call as the underlying node did not seem to exist" );
                                 }
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                 public void clear() throws CacheException {
                                 try {
                                 Option option = new Option();
                                 option.setDataVersion( NonLockingDataVersion.INSTANCE );
                                 cache.remove( regionFqn, option );
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                 public void destroy() throws CacheException {
                                 try {
                                 Option option = new Option();
                                 option.setCacheModeLocal( true );
                                 option.setFailSilently( true );
                                 option.setDataVersion( NonLockingDataVersion.INSTANCE );
                                 cache.remove( regionFqn, option );
                                 }
                                 catch( Exception e ) {
                                 throw new CacheException( e );
                                 }
                                 }
                                
                                 public void lock(Object key) throws CacheException {
                                 throw new UnsupportedOperationException( "TreeCache is a fully transactional cache" + regionName );
                                 }
                                
                                 public void unlock(Object key) throws CacheException {
                                 throw new UnsupportedOperationException( "TreeCache is a fully transactional cache: " + regionName );
                                 }
                                
                                 public long nextTimestamp() {
                                 return System.currentTimeMillis() / 100;
                                 }
                                
                                 public int getTimeout() {
                                 return 600; //60 seconds
                                 }
                                
                                 public String getRegionName() {
                                 return regionName;
                                 }
                                
                                 public long getSizeInMemory() {
                                 return -1;
                                 }
                                
                                 public long getElementCountInMemory() {
                                 try {
                                 Set children = cache.getChildrenNames( regionFqn );
                                 return children == null ? 0 : children.size();
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                 public long getElementCountOnDisk() {
                                 return 0;
                                 }
                                
                                 public Map toMap() {
                                 try {
                                 Map result = new HashMap();
                                 Set childrenNames = cache.getChildrenNames( regionFqn );
                                 if (childrenNames != null) {
                                 Iterator iter = childrenNames.iterator();
                                 while ( iter.hasNext() ) {
                                 Object key = iter.next();
                                 result.put(
                                 key,
                                 cache.get( new Fqn( regionFqn, key ), ITEM )
                                 );
                                 }
                                 }
                                 return result;
                                 }
                                 catch (Exception e) {
                                 throw new CacheException(e);
                                 }
                                 }
                                
                                 public String toString() {
                                 return "OptimisticTreeCache(" + regionName + ')';
                                 }
                                
                                 public static class DataVersionAdapter implements DataVersion {
                                 private final Object currentVersion;
                                 private final Object previousVersion;
                                 private final Comparator versionComparator;
                                 private final String sourceIdentifer;
                                
                                 public DataVersionAdapter(Object currentVersion, Object previousVersion, Comparator versionComparator, String sourceIdentifer) {
                                 this.currentVersion = currentVersion;
                                 this.previousVersion = previousVersion;
                                 this.versionComparator = versionComparator;
                                 this.sourceIdentifer = sourceIdentifer;
                                 log.trace( "created " + this );
                                 }
                                
                                 /**
                                 * newerThan() call is dispatched against the DataVersion currently
                                 * associated with the node; the passed dataVersion param is the
                                 * DataVersion associated with the data we are trying to put into
                                 * the node.
                                 * <p/>
                                 * we are expected to return true in the case where we (the current
                                 * node DataVersion) are newer that then incoming value. Returning
                                 * true here essentially means that a optimistic lock failure has
                                 * occured (because conversely, the value we are trying to put into
                                 * the node is "older than" the value already there...)
                                 */
                                 public boolean newerThan(DataVersion dataVersion) {
                                 log.trace( "checking [" + this + "] against [" + dataVersion + "]" );
                                 if ( dataVersion instanceof CircumventChecksDataVersion ) {
                                 log.trace( "skipping lock checks..." );
                                 return false;
                                 }
                                 else if ( dataVersion instanceof NonLockingDataVersion ) {
                                 // can happen because of the multiple ways Cache.remove()
                                 // can be invoked :(
                                 log.trace( "skipping lock checks..." );
                                 return false;
                                 }
                                 DataVersionAdapter other = ( DataVersionAdapter ) dataVersion;
                                 if ( other.previousVersion == null ) {
                                 log.warn( "Unexpected optimistic lock check on inserting data" );
                                 // work around the "feature" where tree cache is validating the
                                 // inserted node during the next transaction. no idea...
                                 if ( this == dataVersion ) {
                                 log.trace( "skipping lock checks due to same DV instance" );
                                 return false;
                                 }
                                 }
                                 return versionComparator.compare( currentVersion, other.previousVersion ) >= 1;
                                 }
                                
                                 public String toString() {
                                 return super.toString() + " [current=" + currentVersion + ", previous=" + previousVersion + ", src=" + sourceIdentifer + "]";
                                 }
                                 }
                                
                                 /**
                                 * Used in regions where no locking should ever occur. This includes query-caches,
                                 * update-timestamps caches, collection caches, and entity caches where the entity
                                 * is not versioned.
                                 */
                                 public static class NonLockingDataVersion implements DataVersion {
                                 public static final DataVersion INSTANCE = new NonLockingDataVersion();
                                 public boolean newerThan(DataVersion dataVersion) {
                                 log.trace( "non locking lock check...");
                                 return false;
                                 }
                                 }
                                
                                 /**
                                 * Used to signal to a DataVersionAdapter to simply not perform any checks. This
                                 * is currently needed for proper handling of remove() calls for entity cache regions
                                 * (we do not know the version info...).
                                 */
                                 public static class CircumventChecksDataVersion implements DataVersion {
                                 public static final DataVersion INSTANCE = new CircumventChecksDataVersion();
                                 public boolean newerThan(DataVersion dataVersion) {
                                 throw new CacheException( "optimistic locking checks should never happen on CircumventChecksDataVersion" );
                                 }
                                 }
                                }
                                
                                


                                • 13. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                                  Wim De Muynck Newbie

                                  Exceptions all over the place, must be my lucky day! ;) (today is the 13th after all...)

                                  Fortunately for one part it seems a simple beauty fixup, I replaced in your latest OptimisticTreeCache:

                                   public void writeLoad(Object key, Object value, Object currentVersion) {
                                   try {
                                   Option option = new Option();
                                   option.setFailSilently( true );
                                   option.setDataVersion( NonLockingDataVersion.INSTANCE );
                                   cache.remove( new Fqn( regionFqn, key ), "ITEM", option );


                                  by

                                   public void writeLoad(Object key, Object value, Object currentVersion) {
                                   try {
                                   this.remove(key);



                                  for the same reasons you mention in remove().

                                  The bad part: now I get once in a while get a CacheException thrown where it shouldn't. At some point, TreeCache seems to detect parent nodes with higher versions than in the transaction's workspace. I'm still debugging into these. Oddily enough, my quick 'n dirty fix I posted above didn't do that. I'll post as soon as I have a result.

                                  As for your comments:
                                  - The insert warning: it no longer occurs since writeLoad() no longer passes in a previousVersion=null, so now we have the behaviour of the warning which corresponds to the idea. I got confused by the combination of the bug and the text of the warning, never mind.
                                  - The query-cache-0.6-second gap and the CMT edge case you suspect: could you tell where that happens in the code? I'm very interested in looking up these issues since my app is highly concurrent.
                                  - Weird validated node in next transaction: now that is strange. Could you perhaps post a code snippet where it occurs? However, I refer to my earlier post, TreeCache does seem to validate all nodes your transaction has accessed - modified or not. To me this sounds perfectly okay since you effectively read/write lock this way using the benefit of version control. Perhaps you're seeing this?

                                  • 14. Re: TreeCache/Hibernate/JBossAS optimistic locking problem
                                    Steve Ebersole Apprentice

                                     

                                    "floefliep" wrote:
                                    The insert warning: it no longer occurs since writeLoad() no longer passes in a previousVersion=null

                                    Perhaps that is the reasoning. But my experience was that this scenario was actually previously caused by this "(re)validate in any subsequent transaction(s)" check in most cases and that it was much more the "if ( this == dataVersion )" check that stopped the exceptions. But either way...

                                    "floefliep" wrote:
                                    query-cache-0.6-second gap
                                    First, I was wrong about .6 seconds as I forgot a division of the timestamp by 100 in my manual calculation. This basically comes from o.h.c.UpdateTimestampsCache. This is responsible for deciding whether or not cached query results should be considered invalid; it does this through some timestamping. Whenever Hibernate performs DML operations against a set of "table spaces", it notifies the UpdateTimestampsCache in a two phase process (preinvalidate and invalidate). invalidate() is responsible for setting the appropriate timetamp value into the underlying cache region that is then later used in the isUpToDate() checks. However, that invalidate() call happens during Hibernate's after-transaction-completion process. Which of course fails when using TreeCache because it does not like this put() outside the scope of an active transaction. And basically this all means that query caching is totally useless here.

                                    "floefliep" wrote:
                                    CMT edge case

                                    For example, in my tests, it creeps up in trying to clean up the test data. Consider:
                                     s = openSession();
                                     s.delete( item );
                                    

                                    where item is a detached instance and where we are relying on flush-on-completion with CMT. In this case, Hibernate has made no TreeCache calls and thus TreeCache has not had a chance to register its synchronization. Now, at transaction commit Hibernate's synchronization gets a beforeCompletion callback and starts its flush processing; at that point it now calls into TreeCache which causes TreeCache to register its synch - but that is obviously too late in the game since because it registers in the midst of the beforeCompletion phase it will (at least) not get a


                                    "floefliep" wrote:
                                    Weird validated node in next transaction

                                    In my experience, it *appears* that it does not matter if you even access the node at all... But I need to verify this some more.

                                    "floefliep" wrote:
                                    I replaced in your latest OptimisticTreeCache:

                                    So I am missing something. You mean to tell me that TreeCache.remove() *fails* if the node does not exist even when you explicitly specify "fail silently"? I needed the "protection check" in OptimisitcTreeCache.remove() because there I cannot explcitly set the fail-silectly mode...

                                    "floefliep" wrote:
                                    Exceptions all over the place

                                    Other than whats discussed above? What are they?

                                    1 2 Previous Next