FileDocCategorySizeDatePackage
ReadWriteCache.javaAPI DocHibernate 3.2.513140Thu Feb 09 13:48:44 GMT 2006org.hibernate.cache

ReadWriteCache

public class ReadWriteCache extends Object implements CacheConcurrencyStrategy
Caches data that is sometimes updated while maintaining the semantics of "read committed" isolation level. If the database is set to "repeatable read", this concurrency strategy almost maintains the semantics. Repeatable read isolation is compromised in the case of concurrent writes. This is an "asynchronous" concurrency strategy.

If this strategy is used in a cluster, the underlying cache implementation must support distributed hard locks (which are held only momentarily). This strategy also assumes that the underlying cache implementation does not do asynchronous replication and that state has been fully replicated as soon as the lock is released.
see
NonstrictReadWriteCache for a faster algorithm
see
CacheConcurrencyStrategy

Fields Summary
private static final Log
log
private Cache
cache
private int
nextLockId
Constructors Summary
public ReadWriteCache()


	  
Methods Summary
public synchronized booleanafterInsert(java.lang.Object key, java.lang.Object value, java.lang.Object version)
Add the new item to the cache, checking that no other transaction has accessed the item.

	
		if ( log.isTraceEnabled() ) log.trace("Inserting: " + key);
		try {
			cache.lock(key);

			Lockable lockable = (Lockable) cache.get(key);
			if (lockable==null) {
				cache.update( key, new Item( value, version, cache.nextTimestamp() ) );
				if ( log.isTraceEnabled() ) log.trace("Inserted: " + key);
				return true;
			}
			else {
				return false;
			}
		}
		finally {
			cache.unlock(key);
		}
	
public synchronized booleanafterUpdate(java.lang.Object key, java.lang.Object value, java.lang.Object version, SoftLock clientLock)
Re-cache the updated state, if and only if there there are no other concurrent soft locks. Release our lock.

		
		if ( log.isTraceEnabled() ) log.trace("Updating: " + key);

		try {
			cache.lock(key);

			Lockable lockable = (Lockable) cache.get(key);
			if ( isUnlockable(clientLock, lockable) ) {
				Lock lock = (Lock) lockable;
				if ( lock.wasLockedConcurrently() ) {
					// just decrement the lock, don't recache
					// (we don't know which transaction won)
					decrementLock(key, lock);
					return false;
				}
				else {
					//recache the updated state
					cache.update( key, new Item( value, version, cache.nextTimestamp() ) );
					if ( log.isTraceEnabled() ) log.trace("Updated: " + key);
					return true;
				}
			}
			else {
				handleLockExpiry(key);
				return false;
			}

		}
		finally {
			cache.unlock(key);
		}
	
public voidclear()

		cache.clear();
	
private voiddecrementLock(java.lang.Object key, org.hibernate.cache.ReadWriteCache$Lock lock)
decrement a lock and put it back in the cache

		//decrement the lock
		lock.unlock( cache.nextTimestamp() );
		cache.update(key, lock);
	
public voiddestroy()

		try {
			cache.destroy();
		}
		catch (Exception e) {
			log.warn("could not destroy cache", e);
		}
	
public voidevict(java.lang.Object key)
Do nothing.

		// noop
	
public synchronized java.lang.Objectget(java.lang.Object key, long txTimestamp)
Do not return an item whose timestamp is later than the current transaction timestamp. (Otherwise we might compromise repeatable read unnecessarily.) Do not return an item which is soft-locked. Always go straight to the database instead.

Note that since reading an item from that cache does not actually go to the database, it is possible to see a kind of phantom read due to the underlying row being updated after we have read it from the cache. This would not be possible in a lock-based implementation of repeatable read isolation. It is also possible to overwrite changes made and committed by another transaction after the current transaction read the item from the cache. This problem would be caught by the update-time version-checking, if the data is versioned or timestamped.


		if ( log.isTraceEnabled() ) log.trace("Cache lookup: " + key);

		/*try {
			cache.lock(key);*/

			Lockable lockable = (Lockable) cache.get(key);

			boolean gettable = lockable!=null && lockable.isGettable(txTimestamp);

			if (gettable) {
				if ( log.isTraceEnabled() ) log.trace("Cache hit: " + key);
				return ( (Item) lockable ).getValue();
			}
			else {
				if ( log.isTraceEnabled() ) {
					if (lockable==null) {
						log.trace("Cache miss: " + key);
					}
					else {
						log.trace("Cached item was locked: " + key);
					}
				}
				return null;
			}
		/*}
		finally {
			cache.unlock(key);
		}*/
	
public CachegetCache()

		return cache;
	
public java.lang.StringgetRegionName()

		return cache.getRegionName();
	
voidhandleLockExpiry(java.lang.Object key)

		log.warn("An item was expired by the cache while it was locked (increase your cache timeout): " + key);
		long ts = cache.nextTimestamp() + cache.getTimeout();
		// create new lock that times out immediately
		Lock lock = new Lock( ts, nextLockId(), null );
		lock.unlock(ts);
		cache.update(key, lock);
	
public booleaninsert(java.lang.Object key, java.lang.Object value, java.lang.Object currentVersion)
Do nothing.

		return false;
	
private booleanisUnlockable(SoftLock clientLock, org.hibernate.cache.ReadWriteCache$Lockable myLock)
Is the client's lock commensurate with the item in the cache? If it is not, we know that the cache expired the original lock.

		//null clientLock is remotely possible but will never happen in practice
		return myLock!=null &&
			myLock.isLock() &&
			clientLock!=null &&
			( (Lock) clientLock ).getId()==( (Lock) myLock ).getId();
	
public synchronized SoftLocklock(java.lang.Object key, java.lang.Object version)
Stop any other transactions reading or writing this item to/from the cache. Send them straight to the database instead. (The lock does time out eventually.) This implementation tracks concurrent locks of transactions which simultaneously attempt to write to an item.

		if ( log.isTraceEnabled() ) log.trace("Invalidating: " + key);

		try {
			cache.lock(key);

			Lockable lockable = (Lockable) cache.get(key);
			long timeout = cache.nextTimestamp() + cache.getTimeout();
			final Lock lock = (lockable==null) ?
				new Lock( timeout, nextLockId(), version ) :
				lockable.lock( timeout, nextLockId() );
			cache.update(key, lock);
			return lock;
		}
		finally {
			cache.unlock(key);
		}

	
private intnextLockId()
Generate an id for a new lock. Uniqueness per cache instance is very desirable but not absolutely critical. Must be called from one of the synchronized methods of this class.

		if (nextLockId==Integer.MAX_VALUE) nextLockId = Integer.MIN_VALUE;
		return nextLockId++;
	
public synchronized booleanput(java.lang.Object key, java.lang.Object value, long txTimestamp, java.lang.Object version, java.util.Comparator versionComparator, boolean minimalPut)
Do not add an item to the cache unless the current transaction timestamp is later than the timestamp at which the item was invalidated. (Otherwise, a stale item might be re-added if the database is operating in repeatable read isolation mode.) For versioned data, don't add the item unless it is the later version.

		if ( log.isTraceEnabled() ) log.trace("Caching: " + key);

		try {
			cache.lock(key);

			Lockable lockable = (Lockable) cache.get(key);

			boolean puttable = lockable==null || 
				lockable.isPuttable(txTimestamp, version, versionComparator);

			if (puttable) {
				cache.put( key, new Item( value, version, cache.nextTimestamp() ) );
				if ( log.isTraceEnabled() ) log.trace("Cached: " + key);
				return true;
			}
			else {
				if ( log.isTraceEnabled() ) {
					if ( lockable.isLock() ) {
						log.trace("Item was locked: " + key);
					}
					else {
						log.trace("Item was already cached: " + key);
					}
				}
				return false;
			}
		}
		finally {
			cache.unlock(key);
		}
	
public synchronized voidrelease(java.lang.Object key, SoftLock clientLock)
Release the soft lock on the item. Other transactions may now re-cache the item (assuming that no other transaction holds a simultaneous lock).

		if ( log.isTraceEnabled() ) log.trace("Releasing: " + key);

		try {
			cache.lock(key);

			Lockable lockable = (Lockable) cache.get(key);
			if ( isUnlockable(clientLock, lockable) ) {
				decrementLock(key, (Lock) lockable);
			}
			else {
				handleLockExpiry(key);
			}
		}
		finally {
			cache.unlock(key);
		}
	
public voidremove(java.lang.Object key)

		cache.remove(key);
	
public voidsetCache(Cache cache)

		this.cache=cache;
	
public java.lang.StringtoString()

		return cache + "(read-write)";
	
public booleanupdate(java.lang.Object key, java.lang.Object value, java.lang.Object currentVersion, java.lang.Object previousVersion)
Do nothing.

		return false;