package org.jboss.cache.api.pfer;
import org.easymock.EasyMock;
import static org.easymock.EasyMock.*;
import org.jboss.cache.Cache;
import org.jboss.cache.CacheFactory;
import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.NodeSPI;
import org.jboss.cache.RPCManager;
import org.jboss.cache.config.Configuration;
import org.jboss.cache.config.Configuration.CacheMode;
import org.jboss.cache.factories.UnitTestCacheConfigurationFactory;
import org.jboss.cache.lock.NodeLock;
import org.jboss.cache.marshall.MethodCall;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.optimistic.TransactionWorkspace;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.transaction.OptimisticTransactionEntry;
import org.jgroups.Address;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.util.List;
@Test(groups = {"functional", "jgroups", "transaction"})
public abstract class PutForExternalReadTestBase
{
protected CacheSPI<String, String> cache1, cache2;
protected TransactionManager tm1, tm2;
protected Fqn<String> fqn = Fqn.fromString("/one/two");
protected Fqn<String> parentFqn = fqn.getParent();
protected String key = "k", value = "v", value2 = "v2";
protected boolean useTx, optimistic;
protected Configuration.CacheMode cacheMode;
@BeforeMethod(alwaysRun = true)
public void setUp()
{
CacheFactory<String, String> cf = new DefaultCacheFactory();
cache1 = (CacheSPI<String, String>) cf.createCache(UnitTestCacheConfigurationFactory.createConfiguration(cacheMode), false);
cache1.getConfiguration().setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
cache1.getConfiguration().setNodeLockingScheme(optimistic ? Configuration.NodeLockingScheme.OPTIMISTIC : Configuration.NodeLockingScheme.PESSIMISTIC);
// cache1.getConfiguration().setSyncCommitPhase(optimistic);
// cache1.getConfiguration().setSyncRollbackPhase(optimistic);
cache1.start();
tm1 = cache1.getConfiguration().getRuntimeConfig().getTransactionManager();
cache2 = (CacheSPI<String, String>) cf.createCache(UnitTestCacheConfigurationFactory.createConfiguration(cacheMode), false);
cache2.getConfiguration().setTransactionManagerLookupClass("org.jboss.cache.transaction.DummyTransactionManagerLookup");
cache2.getConfiguration().setNodeLockingScheme(optimistic ? Configuration.NodeLockingScheme.OPTIMISTIC : Configuration.NodeLockingScheme.PESSIMISTIC);
// cache2.getConfiguration().setSyncCommitPhase(optimistic);
// cache2.getConfiguration().setSyncRollbackPhase(optimistic);
cache2.start();
tm2 = cache2.getConfiguration().getRuntimeConfig().getTransactionManager();
TestingUtil.blockUntilViewsReceived(10000, cache1, cache2);
}
@AfterMethod(alwaysRun = true)
public void tearDown()
{
TestingUtil.killCaches(cache1, cache2);
}
/**
* Locks could only occur on the parent node is write locked since if the child node exists it is a no-op anyway.
* If the parent node is read locked as well, there is no issue.
*/
public void testNoOpWhenLockedAnd0msTimeout() throws Exception
{
// create the parent node first ...
cache1.put(parentFqn, key, value);
tm1.begin();
cache1.put(parentFqn, key, value2);
NodeSPI parentNode = null;
TransactionWorkspace workspace = null;
if (optimistic)
workspace = extractTransactionWorkspace(cache1);
else
parentNode = (NodeSPI<String, String>) cache1.getRoot().getChild(parentFqn);
Transaction t = tm1.suspend();
assertLocked(parentFqn, parentNode, workspace, true);
// parentFqn should be write-locked.
long startTime = System.currentTimeMillis();
cache1.putForExternalRead(fqn, key, value);
// crappy way to test that pFER does not block, but it is effective.
assertTrue("Should not wait for lock timeout, should attempt to acquite lock with 0ms!", System.currentTimeMillis() - startTime < cache1.getConfiguration().getLockAcquisitionTimeout());
// should not block.
tm1.resume(t);
tm1.commit();
asyncWait();
assertEquals("Parent node write should have succeeded", value2, cache1.get(parentFqn, key));
if (isUsingInvalidation())
assertNull("Parent node write should have invalidated", cache2.get(parentFqn, key));
else
assertEquals("Parent node write should have replicated", value2, cache2.get(parentFqn, key));
if (!optimistic)
{
// doesn't apply with optimistic locking since both txs will succeed here.
assertNull("PFER should have been a no-op", cache1.get(fqn, key));
assertNull("PFER should have been a no-op", cache2.get(fqn, key));
}
}
public void testNoOpWhenNodePresent()
{
cache1.putForExternalRead(fqn, key, value);
asyncWait();
assertEquals("PFER should have succeeded", value, cache1.get(fqn, key));
if (isUsingInvalidation())
assertNull("PFER should not have effected cache2", cache2.get(fqn, key));
else
assertEquals("PFER should have replicated", value, cache2.get(fqn, key));
// reset
cache1.removeNode(fqn);
asyncWait();
assertFalse("Should have reset", cache1.getRoot().hasChild(fqn));
assertFalse("Should have reset", cache2.getRoot().hasChild(fqn));
cache1.put(fqn, key, value);
asyncWait();
// now this pfer should be a no-op
cache1.putForExternalRead(fqn, key, value2);
assertEquals("PFER should have been a no-op", value, cache1.get(fqn, key));
if (isUsingInvalidation())
assertNull("PFER should have been a no-op", cache2.get(fqn, key));
else
assertEquals("PFER should have been a no-op", value, cache2.get(fqn, key));
}
private List<Address> anyAddresses()
{
anyObject();
return null;
}
public void testAsyncForce() throws Exception
{
RPCManager rpcManager = EasyMock.createNiceMock(RPCManager.class);
RPCManager originalRpcManager = cache1.getConfiguration().getRuntimeConfig().getRPCManager();
List<Address> memberList = originalRpcManager.getMembers();
expect(rpcManager.getMembers()).andReturn(memberList).anyTimes();
// inject a mock RPC manager so that we can test whether calls made are sync or async.
TestingUtil.extractComponentRegistry(cache1).registerComponent(RPCManager.class.getName(), rpcManager, RPCManager.class);
// invalidations will not trigger any rpc call sfor PFER
if (!isUsingInvalidation())
{
// specify what we expect called on the mock Rpc Manager. For params we don't care about, just use ANYTHING.
// setting the mock object to expect the "sync" param to be false.
expect(rpcManager.callRemoteMethods(anyAddresses(), (MethodCall) anyObject(), eq(false), anyBoolean(), anyInt(), anyBoolean())).andReturn(null);
}
replay(rpcManager);
// now try a simple replication. Since the RPCManager is a mock object it will not actually replicate anything.
cache1.putForExternalRead(fqn, key, value);
verify(rpcManager);
// cleanup
TestingUtil.extractComponentRegistry(cache1).registerComponent(RPCManager.class.getName(), originalRpcManager, RPCManager.class);
cache1.removeNode(fqn);
}
public void testTxSuspension() throws Exception
{
// create parent node first
cache1.put(parentFqn, key, value);
// start a tx and do some stuff.
tm1.begin();
cache1.get(parentFqn, key);
NodeSPI parentNode = null;
TransactionWorkspace workspace = null;
if (optimistic)
workspace = extractTransactionWorkspace(cache1);
else
parentNode = (NodeSPI<String, String>) cache1.getRoot().getChild(parentFqn);
cache1.putForExternalRead(fqn, key, value); // should have happened in a separate tx and have committed already.
Transaction t = tm1.suspend();
asyncWait();
assertLocked(parentFqn, parentNode, workspace, false);
assertEquals("PFER should have completed", value, cache1.get(fqn, key));
if (isUsingInvalidation())
assertNull("PFER should not have effected cache2", cache2.get(fqn, key));
else
assertEquals("PFER should have completed", value, cache2.get(fqn, key));
tm1.resume(t);
tm1.commit();
asyncWait();
assertEquals("parent fqn tx should have completed", value, cache1.get(parentFqn, key));
if (isUsingInvalidation())
assertNull("parent fqn tx should have invalidated cache2", cache2.get(parentFqn, key));
else
assertEquals("parent fqn tx should have completed", value, cache2.get(parentFqn, key));
}
public void testExceptionSuppression() throws Exception
{
RPCManager barfingRpcManager = EasyMock.createNiceMock(RPCManager.class);
RPCManager originalRpcManager = cache1.getConfiguration().getRuntimeConfig().getRPCManager();
try
{
List<Address> memberList = originalRpcManager.getMembers();
expect(barfingRpcManager.getMembers()).andReturn(memberList).anyTimes();
expect(barfingRpcManager.getLocalAddress()).andReturn(originalRpcManager.getLocalAddress()).anyTimes();
expect(barfingRpcManager.callRemoteMethods(anyAddresses(), (MethodCall) anyObject(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean())).andThrow(new RuntimeException("Barf!")).anyTimes();
replay(barfingRpcManager);
TestingUtil.extractComponentRegistry(cache1).registerComponent(RPCManager.class.getName(), barfingRpcManager, RPCManager.class);
cache1.getConfiguration().getRuntimeConfig().setRPCManager(barfingRpcManager);
try
{
cache1.put(fqn, key, value);
if (!optimistic) fail("Should have barfed");
}
catch (RuntimeException re)
{
}
if (optimistic && !isUsingInvalidation())
{
// proves that the put did, in fact, barf. Doesn't work for invalidations since the inability to invalidate will not cause a rollback.
assertNull(cache1.get(fqn, key));
}
else
{
// clean up any indeterminate state left over
try
{
cache1.removeNode(fqn);
// as above, the inability to invalidate will not cause an exception
if (!isUsingInvalidation()) fail("Should have barfed");
}
catch (RuntimeException re)
{
}
}
assertNull("Should have cleaned up", cache1.get(fqn, key));
// should not barf
cache1.putForExternalRead(fqn, key, value);
}
finally
{
TestingUtil.extractComponentRegistry(cache1).registerComponent(RPCManager.class.getName(), originalRpcManager, RPCManager.class);
}
}
public void testBasicPropagation() throws Exception
{
assert !cache1.exists(fqn);
assert !cache2.exists(fqn);
cache1.putForExternalRead(fqn, key, value);
asyncWait();
assertEquals("PFER updated cache1", value, cache1.get(fqn, key));
Object expected = isUsingInvalidation() ? null : value;
assertEquals("PFER propagated to cache2 as expected", expected, cache2.get(fqn, key));
cache2.putForExternalRead(fqn, key, value);
asyncWait();
assertEquals("PFER updated cache2", value, cache2.get(fqn, key));
assertEquals("PFER propagated to cache1 as expected", value, cache1.get(fqn, key));
}
/**
* Tests that setting a cacheModeLocal=true Option prevents propagation
* of the putForExternalRead().
*
* @throws Exception
*/
public void testSimpleCacheModeLocal() throws Exception
{
cacheModeLocalTest(false);
}
/**
* Tests that setting a cacheModeLocal=true Option prevents propagation
* of the putForExternalRead() when the call occurs inside a transaction.
*
* @throws Exception
*/
public void testCacheModeLocalInTx() throws Exception
{
cacheModeLocalTest(true);
}
/**
* Tests that suspended transactions do not leak. See JBCACHE-1246.
*
* @throws Exception
*/
public void testMemLeakOnSuspendedTransactions() throws Exception
{
Fqn fqn2 = Fqn.fromString("/fqn/two");
tm1.begin();
cache1.putForExternalRead(fqn, key, value);
tm1.commit();
asyncWait();
assert cache1.getTransactionTable().getNumGlobalTransactions() == 0 : "Cache 1 should have no stale global TXs";
assert cache1.getTransactionTable().getNumLocalTransactions() == 0 : "Cache 1 should have no stale local TXs";
assert cache2.getTransactionTable().getNumGlobalTransactions() == 0 : "Cache 2 should have no stale global TXs";
assert cache2.getTransactionTable().getNumLocalTransactions() == 0 : "Cache 2 should have no stale local TXs";
tm1.begin();
cache1.putForExternalRead(fqn, key, value);
cache1.put(fqn2, key, value);
tm1.commit();
asyncWait();
assert cache1.getTransactionTable().getNumGlobalTransactions() == 0 : "Cache 1 should have no stale global TXs";
assert cache1.getTransactionTable().getNumLocalTransactions() == 0 : "Cache 1 should have no stale local TXs";
assert cache2.getTransactionTable().getNumGlobalTransactions() == 0 : "Cache 2 should have no stale global TXs";
assert cache2.getTransactionTable().getNumLocalTransactions() == 0 : "Cache 2 should have no stale local TXs";
tm1.begin();
cache1.put(fqn2, key, value);
cache1.putForExternalRead(fqn, key, value);
tm1.commit();
asyncWait();
assert cache1.getTransactionTable().getNumGlobalTransactions() == 0 : "Cache 1 should have no stale global TXs";
assert cache1.getTransactionTable().getNumLocalTransactions() == 0 : "Cache 1 should have no stale local TXs";
assert cache2.getTransactionTable().getNumGlobalTransactions() == 0 : "Cache 2 should have no stale global TXs";
assert cache2.getTransactionTable().getNumLocalTransactions() == 0 : "Cache 2 should have no stale local TXs";
tm1.begin();
cache1.put(fqn2, key, value);
cache1.putForExternalRead(fqn, key, value);
cache1.put(fqn2, key, value);
tm1.commit();
asyncWait();
assert cache1.getTransactionTable().getNumGlobalTransactions() == 0 : "Cache 1 should have no stale global TXs";
assert cache1.getTransactionTable().getNumLocalTransactions() == 0 : "Cache 1 should have no stale local TXs";
assert cache2.getTransactionTable().getNumGlobalTransactions() == 0 : "Cache 2 should have no stale global TXs";
assert cache2.getTransactionTable().getNumLocalTransactions() == 0 : "Cache 2 should have no stale local TXs";
}
/**
* Tests that setting a cacheModeLocal=true Option prevents propagation
* of the putForExternalRead().
*
* @throws Exception
*/
private void cacheModeLocalTest(boolean transactional) throws Exception
{
RPCManager rpcManager = EasyMock.createMock(RPCManager.class);
RPCManager originalRpcManager = cache1.getConfiguration().getRuntimeConfig().getRPCManager();
// inject a mock RPC manager so that we can test whether calls made are sync or async.
cache1.getConfiguration().getRuntimeConfig().setRPCManager(rpcManager);
// specify that we expect nothing will be called on the mock Rpc Manager.
replay(rpcManager);
// now try a simple replication. Since the RPCManager is a mock object it will not actually replicate anything.
if (transactional)
tm1.begin();
cache1.getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
cache1.putForExternalRead(fqn, key, value);
if (transactional)
tm1.commit();
verify(rpcManager);
// cleanup
cache1.getConfiguration().getRuntimeConfig().setRPCManager(originalRpcManager);
cache1.removeNode(fqn);
}
protected void assertLocked(Fqn fqn, NodeSPI n, TransactionWorkspace workspace, boolean write_locked) throws Exception
{
// this needs to cater for "optimistically locked" nodes as well.
if (workspace != null)
{
// scan workspaces for this node
assertNotNull("node " + fqn + " should be in transaction workspace", workspace.getNode(fqn));
}
else
{
NodeLock lock = n.getLock();
assertTrue("node " + fqn + " is not locked", lock.isLocked());
if (write_locked)
{
assertTrue("node " + fqn + " is not write-locked" + (lock.isReadLocked() ? " but is read-locked instead!" : "!"), lock.isWriteLocked());
}
else
{
assertTrue("node " + fqn + " is not read-locked" + (lock.isWriteLocked() ? " but is write-locked instead!" : "!"), lock.isReadLocked());
}
}
}
protected TransactionWorkspace extractTransactionWorkspace(Cache c)
{
CacheSPI cs = (CacheSPI) c;
try
{
GlobalTransaction gtx = cs.getTransactionTable().get(cs.getTransactionManager().getTransaction());
OptimisticTransactionEntry entry = (OptimisticTransactionEntry) cs.getTransactionTable().get(gtx);
return entry.getTransactionWorkSpace();
}
catch (SystemException e)
{
e.printStackTrace();
fail("Unable to extract transaction workspace from cache");
}
return null;
}
protected void asyncWait()
{
TestingUtil.sleepThread(500);
}
protected boolean isUsingInvalidation()
{
return cacheMode == CacheMode.INVALIDATION_ASYNC
|| cacheMode == CacheMode.INVALIDATION_SYNC;
}
}