[jboss-user] [JBossCache] - Re: TreeCache/Hibernate/JBossAS optimistic locking problem

steve.ebersole@jboss.com do-not-reply at jboss.com
Wed Jul 12 23:07:39 EDT 2006


  | //$Id: OptimisticTreeCache.java 9965 2006-05-30 13:00:28 -0500 (Tue, 30 May 2006) steve.ebersole at 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" );
  | 		}
  | 	}
  | }
  | 
  | 

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

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



More information about the jboss-user mailing list