Allow read methods against transactional caches during the afterCommit or afterRollback phase.

Writes during these phases are still not allowed.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@6680 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-09-05 14:29:29 +00:00
parent 91c83aa5eb
commit 964f88773b
3 changed files with 124 additions and 34 deletions

View File

@@ -33,6 +33,9 @@ import javax.transaction.UserTransaction;
import junit.framework.TestCase; import junit.framework.TestCase;
import net.sf.ehcache.CacheManager; import net.sf.ehcache.CacheManager;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry; import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
@@ -168,13 +171,27 @@ public class CacheTest extends TestCase
assertFalse("Transactionally removed item found in keys", transactionalKeys.contains(newGlobalOne)); assertFalse("Transactionally removed item found in keys", transactionalKeys.contains(newGlobalOne));
assertTrue("Transactionally added item not found in keys", transactionalKeys.contains(updatedTxnThree)); assertTrue("Transactionally added item not found in keys", transactionalKeys.contains(updatedTxnThree));
// Register a post-commit stresser. We do this here so that it is registered after the transactional cache
PostCommitCacheUser listener = new PostCommitCacheUser(transactionalCache, updatedTxnThree);
AlfrescoTransactionSupport.bindListener(listener);
// commit the transaction // commit the transaction
txn.commit(); txn.commit();
// Check the post-commit stresser
if (listener.e != null)
{
throw listener.e;
}
// check that backing cache was updated with the in-transaction changes // check that backing cache was updated with the in-transaction changes
assertFalse("Item was not removed from backing cache", backingCache.contains(newGlobalOne)); assertFalse("Item was not removed from backing cache", backingCache.contains(newGlobalOne));
assertNull("Item could still be fetched from backing cache", backingCache.get(newGlobalOne)); assertNull("Item could still be fetched from backing cache", backingCache.get(newGlobalOne));
assertEquals("Item not updated in backing cache", "XXX", backingCache.get(updatedTxnThree)); assertEquals("Item not updated in backing cache", "XXX", backingCache.get(updatedTxnThree));
// Check that the transactional cache serves get requests
assertEquals("Transactional cache must serve post-commit get requests", "XXX",
transactionalCache.get(updatedTxnThree));
} }
catch (Throwable e) catch (Throwable e)
{ {
@@ -186,6 +203,53 @@ public class CacheTest extends TestCase
} }
} }
/**
* This transaction listener attempts to use the cache in the afterCommit phase. Technically the
* transaction has finished, but the transaction resources are still available.
*
* @author Derek Hulley
* @since 2.1
*/
private class PostCommitCacheUser extends TransactionListenerAdapter
{
private final SimpleCache<String, Object> transactionalCache;
private final String key;
private Throwable e;
private PostCommitCacheUser(SimpleCache<String, Object> transactionalCache, String key)
{
this.transactionalCache = transactionalCache;
this.key = key;
}
@Override
public void afterCommit()
{
try
{
transactionalCache.get(key);
}
catch (Throwable e)
{
this.e = e;
return;
}
try
{
transactionalCache.put(key, "ZZZ");
e = new RuntimeException("Transactional caches should not allow puts in the after-commit phase");
}
catch (AlfrescoRuntimeException e)
{
// Expected
}
}
@Override
public int hashCode()
{
return -100000;
}
}
/** /**
* Preloads the cache, then performs a simultaneous addition of N new values and * Preloads the cache, then performs a simultaneous addition of N new values and
* removal of the N preloaded values. * removal of the N preloaded values.

View File

@@ -291,47 +291,55 @@ public class TransactionalCache<K extends Serializable, V extends Object>
if (AlfrescoTransactionSupport.getTransactionId() != null) if (AlfrescoTransactionSupport.getTransactionId() != null)
{ {
TransactionData txnData = getTransactionData(); TransactionData txnData = getTransactionData();
try if (txnData.isClosed)
{ {
if (!txnData.isClearOn) // deletions cache only useful before a clear // This check could have been done in the first if block, but that would have added another call to the
// txn resources.
}
else // The txn is still active
{
try
{ {
// check to see if the key is present in the transaction's removed items if (!txnData.isClearOn) // deletions cache only useful before a clear
if (txnData.removedItemsCache.get(key) != null)
{ {
// it has been removed in this transaction // check to see if the key is present in the transaction's removed items
if (txnData.removedItemsCache.get(key) != null)
{
// it has been removed in this transaction
if (isDebugEnabled)
{
logger.debug("get returning null - item has been removed from transactional cache: \n" +
" cache: " + this + "\n" +
" key: " + key);
}
return null;
}
}
// check for the item in the transaction's new/updated items
Element element = txnData.updatedItemsCache.get(key);
if (element != null)
{
CacheBucket<V> bucket = (CacheBucket<V>) element.getValue();
V value = bucket.getValue();
// element was found in transaction-specific updates/additions
if (isDebugEnabled) if (isDebugEnabled)
{ {
logger.debug("get returning null - item has been removed from transactional cache: \n" + logger.debug("Found item in transactional cache: \n" +
" cache: " + this + "\n" + " cache: " + this + "\n" +
" key: " + key); " key: " + key + "\n" +
" value: " + value);
} }
return null; return value;
} }
} }
catch (CacheException e)
// check for the item in the transaction's new/updated items
Element element = txnData.updatedItemsCache.get(key);
if (element != null)
{ {
CacheBucket<V> bucket = (CacheBucket<V>) element.getValue(); throw new AlfrescoRuntimeException("Cache failure", e);
V value = bucket.getValue();
// element was found in transaction-specific updates/additions
if (isDebugEnabled)
{
logger.debug("Found item in transactional cache: \n" +
" cache: " + this + "\n" +
" key: " + key + "\n" +
" value: " + value);
}
return value;
} }
// check if the cleared flag has been set - cleared flag means ignore shared as unreliable
ignoreSharedCache = txnData.isClearOn;
} }
catch (CacheException e)
{
throw new AlfrescoRuntimeException("Cache failure", e);
}
// check if the cleared flag has been set - cleared flag means ignore shared as unreliable
ignoreSharedCache = txnData.isClearOn;
} }
// no value found - must we ignore the shared cache? // no value found - must we ignore the shared cache?
if (!ignoreSharedCache) if (!ignoreSharedCache)
@@ -384,6 +392,11 @@ public class TransactionalCache<K extends Serializable, V extends Object>
else // transaction present else // transaction present
{ {
TransactionData txnData = getTransactionData(); TransactionData txnData = getTransactionData();
// Ensure that the cache isn't being modified
if (txnData.isClosed)
{
throw new AlfrescoRuntimeException("onCommit cache modifications are not allowed.");
}
// we have a transaction - add the item into the updated cache for this transaction // we have a transaction - add the item into the updated cache for this transaction
// are we in an overflow condition? // are we in an overflow condition?
if (txnData.updatedItemsCache.getMemoryStoreSize() >= maxCacheSize) if (txnData.updatedItemsCache.getMemoryStoreSize() >= maxCacheSize)
@@ -444,6 +457,11 @@ public class TransactionalCache<K extends Serializable, V extends Object>
else // transaction present else // transaction present
{ {
TransactionData txnData = getTransactionData(); TransactionData txnData = getTransactionData();
// Ensure that the cache isn't being modified
if (txnData.isClosed)
{
throw new AlfrescoRuntimeException("onCommit cache modifications are not allowed.");
}
// is the shared cache going to be cleared? // is the shared cache going to be cleared?
if (txnData.isClearOn) if (txnData.isClearOn)
{ {
@@ -509,6 +527,11 @@ public class TransactionalCache<K extends Serializable, V extends Object>
} }
TransactionData txnData = getTransactionData(); TransactionData txnData = getTransactionData();
// Ensure that the cache isn't being modified
if (txnData.isClosed)
{
throw new AlfrescoRuntimeException("onCommit cache modifications are not allowed.");
}
// the shared cache must be cleared at the end of the transaction // the shared cache must be cleared at the end of the transaction
// and also serves to ensure that the shared cache will be ignored // and also serves to ensure that the shared cache will be ignored
// for the remainder of the transaction // for the remainder of the transaction
@@ -630,6 +653,7 @@ public class TransactionalCache<K extends Serializable, V extends Object>
{ {
cacheManager.removeCache(txnData.updatedItemsCache.getName()); cacheManager.removeCache(txnData.updatedItemsCache.getName());
cacheManager.removeCache(txnData.removedItemsCache.getName()); cacheManager.removeCache(txnData.removedItemsCache.getName());
txnData.isClosed = true;
} }
/** /**
@@ -760,8 +784,9 @@ public class TransactionalCache<K extends Serializable, V extends Object>
/** Data holder to bind data to the transaction */ /** Data holder to bind data to the transaction */
private class TransactionData private class TransactionData
{ {
public Cache updatedItemsCache; private Cache updatedItemsCache;
public Cache removedItemsCache; private Cache removedItemsCache;
public boolean isClearOn; private boolean isClearOn;
private boolean isClosed;
} }
} }

View File

@@ -27,6 +27,7 @@ package org.alfresco.repo.transaction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -461,7 +462,7 @@ public abstract class AlfrescoTransactionSupport
private final Set<TransactionalDao> daoServices; private final Set<TransactionalDao> daoServices;
private final Set<IntegrityChecker> integrityCheckers; private final Set<IntegrityChecker> integrityCheckers;
private final Set<LuceneIndexerAndSearcher> lucenes; private final Set<LuceneIndexerAndSearcher> lucenes;
private final Set<TransactionListener> listeners; private final LinkedHashSet<TransactionListener> listeners;
private final Map<Object, Object> resources; private final Map<Object, Object> resources;
/** /**
@@ -476,7 +477,7 @@ public abstract class AlfrescoTransactionSupport
daoServices = new HashSet<TransactionalDao>(3); daoServices = new HashSet<TransactionalDao>(3);
integrityCheckers = new HashSet<IntegrityChecker>(3); integrityCheckers = new HashSet<IntegrityChecker>(3);
lucenes = new HashSet<LuceneIndexerAndSearcher>(3); lucenes = new HashSet<LuceneIndexerAndSearcher>(3);
listeners = new HashSet<TransactionListener>(5); listeners = new LinkedHashSet<TransactionListener>(5);
resources = new HashMap<Object, Object>(17); resources = new HashMap<Object, Object>(17);
} }