From: <hib...@li...> - 2006-07-13 21:39:30
|
Author: ste...@jb... Date: 2006-07-13 17:38:41 -0400 (Thu, 13 Jul 2006) New Revision: 10118 Modified: trunk/Hibernate3/src/org/hibernate/cache/OptimisticTreeCache.java trunk/Hibernate3/src/org/hibernate/cache/StandardQueryCache.java trunk/Hibernate3/src/org/hibernate/cache/UpdateTimestampsCache.java Log: corrections to OptimisticTreeCache; added some logging; formatting Modified: trunk/Hibernate3/src/org/hibernate/cache/OptimisticTreeCache.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/cache/OptimisticTreeCache.java 2006-07-13 04:06:48 UTC (rev 10117) +++ trunk/Hibernate3/src/org/hibernate/cache/OptimisticTreeCache.java 2006-07-13 21:38:41 UTC (rev 10118) @@ -56,23 +56,15 @@ public void writeUpdate(Object key, Object value, Object currentVersion, Object previousVersion) { try { - Option option = null; - if ( source != null ) { - if ( source.isVersioned() ) { - option = new Option(); - option.setDataVersion( - new DataVersionAdapter( - currentVersion, - previousVersion, - source.getVersionComparator() - ) - ); - } - } + 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); + catch ( Exception e ) { + throw new CacheException( e ); } } @@ -80,18 +72,15 @@ try { Option option = new Option(); option.setFailSilently( true ); + option.setDataVersion( NonLockingDataVersion.INSTANCE ); cache.remove( new Fqn( regionFqn, key ), "ITEM", option ); - if ( source != null ) { - if ( source.isVersioned() ) { - option.setDataVersion( - new DataVersionAdapter( - currentVersion, - null, - source.getVersionComparator() - ) - ); - } - } + + 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) { @@ -106,6 +95,7 @@ try { Option option = new Option(); option.setFailSilently( true ); +// option.setDataVersion( NonLockingDataVersion.INSTANCE ); return cache.get( new Fqn( regionFqn, key ), ITEM, option ); } catch (Exception e) { @@ -124,7 +114,9 @@ public void update(Object key, Object value) throws CacheException { try { - cache.put( new Fqn( regionFqn, key ), ITEM, value ); + Option option = new Option(); + option.setDataVersion( NonLockingDataVersion.INSTANCE ); + cache.put( new Fqn( regionFqn, key ), ITEM, value, option ); } catch (Exception e) { throw new CacheException(e); @@ -133,9 +125,11 @@ 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) { @@ -149,7 +143,16 @@ public void remove(Object key) throws CacheException { try { - cache.remove( new Fqn( regionFqn, key ) ); + // 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); @@ -158,7 +161,9 @@ public void clear() throws CacheException { try { - cache.remove( regionFqn ); + Option option = new Option(); + option.setDataVersion( NonLockingDataVersion.INSTANCE ); + cache.remove( regionFqn, option ); } catch (Exception e) { throw new CacheException(e); @@ -169,6 +174,8 @@ try { Option option = new Option(); option.setCacheModeLocal( true ); + option.setFailSilently( true ); + option.setDataVersion( NonLockingDataVersion.INSTANCE ); cache.remove( regionFqn, option ); } catch( Exception e ) { @@ -243,19 +250,80 @@ private final Object currentVersion; private final Object previousVersion; private final Comparator versionComparator; + private final String sourceIdentifer; - public DataVersionAdapter(Object currentVersion, Object previousVersion, Comparator versionComparator) { + 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) { - if ( previousVersion == null ) { - log.warn( "Unexpected optimistic lock check on inserted data" ); + log.trace( "checking [" + this + "] against [" + dataVersion + "]" ); + if ( dataVersion instanceof CircumventChecksDataVersion ) { + log.trace( "skipping lock checks..." ); + return false; } - Object other = ( ( DataVersionAdapter ) dataVersion ).currentVersion; - return versionComparator.compare( previousVersion, other ) > 1; + 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" ); + } + } } Modified: trunk/Hibernate3/src/org/hibernate/cache/StandardQueryCache.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/cache/StandardQueryCache.java 2006-07-13 04:06:48 UTC (rev 10117) +++ trunk/Hibernate3/src/org/hibernate/cache/StandardQueryCache.java 2006-07-13 21:38:41 UTC (rev 10118) @@ -27,7 +27,7 @@ */ public class StandardQueryCache implements QueryCache { - private static final Log log = LogFactory.getLog(StandardQueryCache.class); + private static final Log log = LogFactory.getLog( StandardQueryCache.class ); private Cache queryCache; private UpdateTimestampsCache updateTimestampsCache; @@ -41,34 +41,40 @@ final Settings settings, final Properties props, final UpdateTimestampsCache updateTimestampsCache, - String regionName) - throws HibernateException { - - if (regionName==null) regionName = StandardQueryCache.class.getName(); + String regionName) throws HibernateException { + if ( regionName == null ) { + regionName = StandardQueryCache.class.getName(); + } String prefix = settings.getCacheRegionPrefix(); - if (prefix!=null) regionName = prefix + '.' + regionName; - - log.info("starting query cache at region: " + regionName); - + if ( prefix != null ) { + regionName = prefix + '.' + regionName; + } + log.info( "starting query cache at region: " + regionName ); + this.queryCache = settings.getCacheProvider().buildCache(regionName, props); this.updateTimestampsCache = updateTimestampsCache; this.regionName = regionName; } - public boolean put(QueryKey key, Type[] returnTypes, List result, boolean isNaturalKeyLookup, SessionImplementor session) - throws HibernateException { + public boolean put( + QueryKey key, + Type[] returnTypes, + List result, + boolean isNaturalKeyLookup, + SessionImplementor session) throws HibernateException { if ( isNaturalKeyLookup && result.size()==0 ) { return false; } else { - + Long ts = new Long( session.getTimestamp() ); + if ( log.isDebugEnabled() ) { - log.debug("caching query results in region: " + regionName); + log.debug( "caching query results in region: " + regionName + "; timestamp=" + ts ); } List cacheable = new ArrayList( result.size()+1 ); - cacheable.add( new Long( session.getTimestamp() ) ); + cacheable.add( ts ); for ( int i=0; i<result.size(); i++ ) { if ( returnTypes.length==1 ) { cacheable.add( returnTypes[0].disassemble( result.get(i), session, null ) ); @@ -86,25 +92,28 @@ } - public List get(QueryKey key, Type[] returnTypes, boolean isNaturalKeyLookup, Set spaces, SessionImplementor session) - throws HibernateException { - + public List get( + QueryKey key, + Type[] returnTypes, + boolean isNaturalKeyLookup, + Set spaces, + SessionImplementor session) throws HibernateException { if ( log.isDebugEnabled() ) { log.debug("checking cached query results in region: " + regionName); } - + List cacheable = (List) queryCache.get(key); if (cacheable==null) { log.debug("query results were not found in cache"); return null; } - + Long timestamp = (Long) cacheable.get(0); if ( !isNaturalKeyLookup && !isUpToDate(spaces, timestamp) ) { log.debug("cached query results were not up to date"); return null; } - + log.debug("returning cached query results"); for ( int i=1; i<cacheable.size(); i++ ) { if ( returnTypes.length==1 ) { Modified: trunk/Hibernate3/src/org/hibernate/cache/UpdateTimestampsCache.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/cache/UpdateTimestampsCache.java 2006-07-13 04:06:48 UTC (rev 10117) +++ trunk/Hibernate3/src/org/hibernate/cache/UpdateTimestampsCache.java 2006-07-13 21:38:41 UTC (rev 10118) @@ -18,37 +18,37 @@ * to a higher value than the timeouts of any of the query caches. In fact, we * recommend that the the underlying cache not be configured for expiry at all. * Note, in particular, that an LRU cache expiry policy is never appropriate. - * @author Gavin King, Mikheil Kapanadze + * + * @author Gavin King + * @author Mikheil Kapanadze */ public class UpdateTimestampsCache { + public static final String REGION_NAME = UpdateTimestampsCache.class.getName(); + private static final Log log = LogFactory.getLog(UpdateTimestampsCache.class); private Cache updateTimestamps; private final String regionName; - public static final String REGION_NAME = UpdateTimestampsCache.class.getName(); - public void clear() throws CacheException { updateTimestamps.clear(); } - public UpdateTimestampsCache(Settings settings, Properties props) - throws HibernateException { + public UpdateTimestampsCache(Settings settings, Properties props) throws HibernateException { String prefix = settings.getCacheRegionPrefix(); - - regionName = prefix==null ? - REGION_NAME : - prefix + '.' + REGION_NAME; - log.info("starting update timestamps cache at region: " + regionName); - this.updateTimestamps = settings.getCacheProvider().buildCache(regionName, props); + regionName = prefix == null ? REGION_NAME : prefix + '.' + REGION_NAME; + log.info( "starting update timestamps cache at region: " + regionName ); + this.updateTimestamps = settings.getCacheProvider().buildCache( regionName, props ); } public synchronized void preinvalidate(Serializable[] spaces) throws CacheException { //TODO: to handle concurrent writes correctly, this should return a Lock to the client Long ts = new Long( updateTimestamps.nextTimestamp() + updateTimestamps.getTimeout() ); for ( int i=0; i<spaces.length; i++ ) { - if ( log.isDebugEnabled() ) log.debug("Pre-invalidating space [" + spaces[i] + "]"); + if ( log.isDebugEnabled() ) { + log.debug( "Pre-invalidating space [" + spaces[i] + "]" ); + } //put() has nowait semantics, is this really appropriate? //note that it needs to be async replication, never local or sync updateTimestamps.put( spaces[i], ts ); @@ -61,7 +61,9 @@ Long ts = new Long( updateTimestamps.nextTimestamp() ); //TODO: if lock.getTimestamp().equals(ts) for ( int i=0; i<spaces.length; i++ ) { - if ( log.isDebugEnabled() ) log.debug("Invalidating space [" + spaces[i] + "], timestamp: " + ts); + if ( log.isDebugEnabled() ) { + log.debug( "Invalidating space [" + spaces[i] + "], timestamp: " + ts); + } //put() has nowait semantics, is this really appropriate? //note that it needs to be async replication, never local or sync updateTimestamps.put( spaces[i], ts ); @@ -83,7 +85,8 @@ if ( log.isDebugEnabled() ) { log.debug("[" + space + "] last update timestamp: " + lastUpdate + ", result set timestamp: " + timestamp ); } - if ( lastUpdate.longValue() >= timestamp.longValue() ) return false; + return lastUpdate.longValue() < timestamp.longValue(); +// if ( lastUpdate.longValue() >= timestamp.longValue() ) return false; } } return true; |