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); } } } }