PojoCache(1.4.1SP9) is corrupted when tx modify a map and ro
oferunipier Jun 13, 2008 5:20 AMIn my cluster i have 2 PojoCache (REPL_SYNC,inactive on startup,transcational,replication version 1.4.0.GA). First i create one cache and fill it with data, then i create the second cache and force full state transfer using region activation. On the second cache, after it received the state, in a new transaction, i fecth an object from the cache and modify a map it contains and then rollback the transaction. After the rollback, *all* the objects in the seconds cache are corrupted: all properties values are now null!, (the other cache is not corrupted). Note, when modifing a primitive property and then rollback, the cache is not corrupted. What i found is that in the corrupted objects, the instanceAdvisor appendedInterceptors list is missing the CacheInterceptor.
attached a unit test demonstrating this bug. The test is based on the distribution org.jboss.cache.aop.statetransfer.StateTransferAopTestBase unit test, to run it, add it to the distribution org.jboss.cache.aop.statetransfer tests package and run the tests.
package org.jboss.cache.aop.statetransfer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import junit.framework.TestCase;
import org.jboss.cache.DummyTransactionManagerLookup;
import org.jboss.cache.Fqn;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.aop.PojoCache;
import org.jboss.cache.aop.test.Address;
import org.jboss.cache.aop.test.Person;
import org.jboss.cache.misc.TestingUtil;
import org.jboss.cache.transaction.DummyTransactionManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class StateTransferFollowedByRollback extends TestCase {
private static Log log = LogFactory.getLog(StateTransferFollowedByRollback.class);
public static final Fqn A_B_1 = Fqn.fromString("/a/b/1");
public static final Fqn A_B_2 = Fqn.fromString("/a/b/2");
private Person joe;
private Person jane;
private Address addr1;
PojoCache cache1;
PojoCache cache2;
public static final Integer TWENTY = new Integer(20);
public static final Integer TWENTYFIVE = new Integer(25);
public void testInitialStateTransferFollowedByMapModificationAndRollback() throws Exception {
log.info("Enter testInitialStateTransferFollowedByMapModificationAndRollback");
//fill the the first cache
cache1 = createCache( true, true, false);
cache1.setTransactionManagerLookup(new DummyTransactionManagerLookup());
log.info("\n\nload the first cache\n\n");
Transaction tx = startTransaction();
cache1.activateRegion("/a");
cache1.putObject(A_B_1, joe);
cache1.putObject(A_B_2, jane);
tx.commit();
cache2 = createCache( true, true, false);
cache2.setTransactionManagerLookup(new DummyTransactionManagerLookup());
// Pause to give caches time to see each other
TestingUtil.blockUntilViewsReceived(new PojoCache[]
{cache1, cache2}, 60000);
//recieve the state
log.info("\n\njoin the first cache\n\n");
tx = startTransaction();
cache2.activateRegion("/a");
tx.commit();
tx = startTransaction();
Person ab1 = (Person) cache2.getObject(A_B_1);
Person ab2 = (Person) cache2.getObject(A_B_2);
assertEquals("Name for /a/b/1 is Joe", joe.getName(), ab1.getName());
assertEquals("City for /a/b/1 is Anytown", addr1.getCity(), ab1.getAddress().getCity());
assertEquals("Hobbies for /a/b/1 are Biking and Jazz", joe.getHobbies(), ab1.getHobbies());
assertEquals("Name for /a/b/2 is Jane", jane.getName(), ab2.getName());
assertEquals("City for /a/b/2 is Anytown", addr1.getCity(), ab2.getAddress().getCity());
assertEquals("Hobbies for /a/b/2 are Bridge and Country", jane.getHobbies(), ab2.getHobbies());
assertTrue("Joe and Jane have same Address", ab1.getAddress() == ab2.getAddress());
tx.commit();
//modify a map in the cached object and rollback
log.info("\n\nrollback on second cache\n\n");
tx = startTransaction();
ab1 = (Person)cache2.getObject(A_B_1);
ab1.getHobbies().put("Sport", "Tennis");
tx.rollback();
tx = startTransaction();
ab1 = (Person) cache2.getObject(A_B_1);
ab2 = (Person) cache2.getObject(A_B_2);
assertEquals("Name for /a/b/1 is Joe", joe.getName(), ab1.getName());
assertEquals("City for /a/b/1 is Anytown", addr1.getCity(), ab1.getAddress().getCity());
assertEquals("Hobbies for /a/b/1 are Biking and Jazz", joe.getHobbies(), ab1.getHobbies());
assertEquals("Name for /a/b/2 is Jane", jane.getName(), ab2.getName());
assertEquals("City for /a/b/2 is Anytown", addr1.getCity(), ab2.getAddress().getCity());
assertEquals("Hobbies for /a/b/2 are Bridge and Country", jane.getHobbies(), ab2.getHobbies());
assertTrue("Joe and Jane have same Address", ab1.getAddress() == ab2.getAddress());
tx.commit();
}
public void testInitialStateTransferFollowedByPrimitiveModificationAndRollback() throws Exception {
log.info("Enter testInitialStateTransferFollowedByPrimitiveModificationAndRollback");
//fill the the first cache
cache1 = createCache(true, true, false);
cache1.setTransactionManagerLookup(new DummyTransactionManagerLookup());
log.info("\n\nload the first cache\n\n");
Transaction tx = startTransaction();
cache1.activateRegion("/a");
cache1.putObject(A_B_1, joe);
cache1.putObject(A_B_2, jane);
tx.commit();
cache2 = createCache(true, true, false);
cache2.setTransactionManagerLookup(new DummyTransactionManagerLookup());
// Pause to give caches time to see each other
TestingUtil.blockUntilViewsReceived(new PojoCache[]
{cache1, cache2}, 60000);
//recieve the state
log.info("\n\njoin the first cache\n\n");
tx = startTransaction();
cache2.activateRegion("/a");
tx.commit();
tx = startTransaction();
Person ab1 = (Person) cache2.getObject(A_B_1);
Person ab2 = (Person) cache2.getObject(A_B_2);
assertEquals("Name for /a/b/1 is Joe", joe.getName(), ab1.getName());
assertEquals("City for /a/b/1 is Anytown", addr1.getCity(), ab1.getAddress().getCity());
assertEquals("Hobbies for /a/b/1 are Biking and Jazz", joe.getHobbies(), ab1.getHobbies());
assertEquals("Name for /a/b/2 is Jane", jane.getName(), ab2.getName());
assertEquals("City for /a/b/2 is Anytown", addr1.getCity(), ab2.getAddress().getCity());
assertEquals("Hobbies for /a/b/2 are Bridge and Country", jane.getHobbies(), ab2.getHobbies());
assertTrue("Joe and Jane have same Address", ab1.getAddress() == ab2.getAddress());
tx.commit();
//modify a string in the cached object and rollback
log.info("\n\nrollback on second cache\n\n");
tx = startTransaction();
ab1 = (Person)cache2.getObject(A_B_1);
ab1.setName("blabla");
tx.rollback();
tx = startTransaction();
ab1 = (Person) cache2.getObject(A_B_1);
ab2 = (Person) cache2.getObject(A_B_2);
assertEquals("Name for /a/b/1 is Joe", joe.getName(), ab1.getName());
assertEquals("City for /a/b/1 is Anytown", addr1.getCity(), ab1.getAddress().getCity());
assertEquals("Hobbies for /a/b/1 are Biking and Jazz", joe.getHobbies(), ab1.getHobbies());
assertEquals("Name for /a/b/2 is Jane", jane.getName(), ab2.getName());
assertEquals("City for /a/b/2 is Anytown", addr1.getCity(), ab2.getAddress().getCity());
assertEquals("Hobbies for /a/b/2 are Bridge and Country", jane.getHobbies(), ab2.getHobbies());
assertTrue("Joe and Jane have same Address", ab1.getAddress() == ab2.getAddress());
tx.commit();
}
private Transaction startTransaction() throws SystemException, NotSupportedException {
DummyTransactionManager mgr=DummyTransactionManager.getInstance();
mgr.begin();
return mgr.getTransaction();
}
protected String getReplicationVersion() {
return "1.4.0.GA";
}
protected void setUp() throws Exception {
super.setUp();
addr1 = new Address();
addr1.setStreet("101 Oakview Dr");
addr1.setCity("Anytown");
addr1.setZip(11111);
joe = new Person();
joe.setName("Joe");
joe.setAge(TWENTY.intValue());
joe.setAddress(addr1);
Set skills = new HashSet();
skills.add("TENNIS");
skills.add("CARPENTRY");
joe.setSkills(skills);
Map hobbies = new HashMap();
hobbies.put("Sport", "Biking");
hobbies.put("Music", "Jazz");
joe.setHobbies(hobbies);
jane = new Person();
jane.setName("Jane");
jane.setAge(TWENTYFIVE.intValue());
jane.setAddress(addr1);
skills = new HashSet();
skills.add("JUJITSU");
skills.add("MACRAME");
jane.setSkills(skills);
hobbies = new HashMap();
hobbies.put("Sport", "Bridge");
hobbies.put("Music", "Country");
jane.setHobbies(hobbies);
}
protected void tearDown() throws Exception {
super.tearDown();
stopCache(cache1);
stopCache(cache2);
//TODO what about cleanFile?
}
protected PojoCache createCache(boolean sync, boolean useMarshalling, boolean useCacheLoader)
throws Exception {
return createCache( sync, useMarshalling, useCacheLoader, true);
}
protected PojoCache createCache(boolean sync,
boolean useMarshalling,
boolean useCacheLoader,
boolean inactiveOnStartup) throws Exception {
PojoCache tree = new PojoCache();
PropertyConfigurator config = new PropertyConfigurator();
String configFile = sync ? "META-INF/replSync-service.xml" : "META-INF/replAsync-service.xml";
config.configure(tree, configFile); // read in generic replAsync xml
//tree.setDeadlockDetection(sync);
tree.setClusterName("StateTransferTestBase");
tree.setReplicationVersion(getReplicationVersion());
// Use a long timeout to facilitate setting debugger breakpoints
tree.setInitialStateRetrievalTimeout(60000);
if (useMarshalling)
{
tree.setUseMarshalling(true);
tree.setInactiveOnStartup(inactiveOnStartup);
}
if (useCacheLoader)
{
//TODO configureCacheLoader(tree, useMarshalling);
}
tree.createService();
tree.startService();
return tree;
}
protected void stopCache(PojoCache cache) {
if (cache != null)
{
try
{
TransactionManager m = cache.getTransactionManager();
try
{
if (m!= null && m.getTransaction() != null)
{
m.rollback();
}
}
catch (Exception e)
{
}
cache.stopService();
cache.destroyService();
}
catch (Exception e)
{
log.error("Exception stopping cache " + e.getMessage(), e);
}
}
}
}