package org.infinispan.tx; import static org.infinispan.test.concurrent.StateSequencerUtil.advanceOnGlobalComponentMethod; import static org.infinispan.test.concurrent.StateSequencerUtil.advanceOnInterceptor; import static org.infinispan.test.concurrent.StateSequencerUtil.matchCommand; import static org.infinispan.test.concurrent.StateSequencerUtil.matchMethodCall; import static org.testng.AssertJUnit.assertEquals; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.infinispan.commands.tx.VersionedCommitCommand; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.distribution.MagicKey; import org.infinispan.statetransfer.StateTransferInterceptor; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.concurrent.InvocationMatcher; import org.infinispan.test.concurrent.StateSequencer; import org.infinispan.test.fwk.CleanupAfterMethod; import org.infinispan.test.fwk.TransportFlags; import org.infinispan.topology.ClusterTopologyManager; import org.infinispan.transaction.LockingMode; import org.infinispan.transaction.impl.LocalTransaction; import org.infinispan.transaction.xa.GlobalTransaction; import org.infinispan.util.ControlledConsistentHashFactory; import org.infinispan.util.concurrent.IsolationLevel; import org.testng.annotations.Test; /** * Test that the modifications of a transaction that were not committed on a node because it didn't own all the keys * are still applied after the node becomes an owner for all of them. * * @author Dan Berindei */ @CleanupAfterMethod @Test(groups = "functional", testName = "tx.OptimisticPartialCommitTest") public class OptimisticPartialCommitTest extends MultipleCacheManagersTest { ControlledConsistentHashFactory controlledCHFactory; @Override protected void createCacheManagers() throws Throwable { controlledCHFactory = new ControlledConsistentHashFactory(new int[]{1, 2}, new int[]{2, 3}); ConfigurationBuilder configuration = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC, true); configuration.clustering().cacheMode(CacheMode.DIST_SYNC); configuration.clustering().hash().numSegments(2).numOwners(2).consistentHashFactory(controlledCHFactory); configuration.transaction().lockingMode(LockingMode.OPTIMISTIC) .locking().isolationLevel(IsolationLevel.REPEATABLE_READ); for (int i = 0; i < 4; i++) { addClusterEnabledCacheManager(configuration, new TransportFlags().withFD(true)); } waitForClusterToForm(); } public void testNonOwnerBecomesOwnerDuringCommit() throws Exception { final Object k1 = new MagicKey("k1", cache(1), cache(2)); final Object k2 = new MagicKey("k2", cache(2), cache(3)); cache(0).put(k1, "v1_0"); cache(0).put(k2, "v2_0"); // commit on cache 0 -> send commit to 1, 2, 3 -> block commit on 2 -> wait for the commit on 1 to finish // -> kill 3 -> rebalance -> 1 applies state from 2 -> 2 resends commit to 1 -> 1 commits again (including k2) // Without the fix, the second commit is ignored and k2 is not updated StateSequencer ss = new StateSequencer(); ss.logicalThread("main", "after_commit_on_1", "before_kill_3", "after_state_applied_on_1", "before_commit_on_2", "after_commit_on_2"); advanceOnInterceptor(ss, cache(1), StateTransferInterceptor.class, matchCommand(VersionedCommitCommand.class).matchCount(0).build()) .after("after_commit_on_1"); advanceOnInterceptor(ss, cache(2), StateTransferInterceptor.class, matchCommand(VersionedCommitCommand.class).matchCount(0).build()) .before("before_commit_on_2").after("after_commit_on_2"); InvocationMatcher stateAppliedOn0Matcher = matchMethodCall("handleRebalancePhaseConfirm") .withParam(1, address(1)).build(); advanceOnGlobalComponentMethod(ss, manager(0), ClusterTopologyManager.class, stateAppliedOn0Matcher) .after("after_state_applied_on_1"); Future<Object> txFuture = fork(() -> { tm(0).begin(); try { cache(0).put(k1, "v1_1"); cache(0).put(k2, "v2_1"); } finally { tm(0).commit(); } return null; }); ss.advance("before_kill_3"); controlledCHFactory.setOwnerIndexes(new int[]{1, 2}, new int[]{2, 1}); manager(3).stop(); cacheManagers.remove(3); txFuture.get(30, TimeUnit.SECONDS); assertEquals("v1_1", cache(1).get(k1)); assertEquals("v2_1", cache(1).get(k2)); assertEquals("v1_1", cache(2).get(k1)); assertEquals("v2_1", cache(2).get(k2)); } public void testOriginatorBecomesOwnerDuringCommit() throws Exception { final Object k1 = new MagicKey("k1", cache(1), cache(2)); final Object k2 = new MagicKey("k2", cache(2), cache(3)); cache(1).put(k1, "v1_0"); cache(1).put(k2, "v2_0"); // commit on cache 1 -> send commit to 2 and 3 -> kill 3 // -> rebalance -> 1 receives state from 2 -> 2 doesn't resend commit to 1 -> 1 finishes commit // Cache 1 wrapped k2 before the prepare, so it will update it during commit even without repeating the commit StateSequencer ss = new StateSequencer(); ss.logicalThread("main", "before_kill_3", "after_state_applied_on_1", "before_commit_on_2", "after_commit_on_2", "after_commit_on_1"); advanceOnInterceptor(ss, cache(1), StateTransferInterceptor.class, command -> { if (!(command instanceof VersionedCommitCommand)) return false; GlobalTransaction gtx = ((VersionedCommitCommand) command).getGlobalTransaction(); LocalTransaction tx = transactionTable(1).getLocalTransaction(gtx); return tx.getStateTransferFlag() == null; }).after("after_commit_on_1"); advanceOnInterceptor(ss, cache(2), StateTransferInterceptor.class, matchCommand(VersionedCommitCommand.class).matchCount(0).build()) .before("before_commit_on_2").after("after_commit_on_2"); InvocationMatcher stateAppliedOn0Matcher = matchMethodCall("handleRebalancePhaseConfirm") .withParam(1, address(1)).build(); advanceOnGlobalComponentMethod(ss, manager(0), ClusterTopologyManager.class, stateAppliedOn0Matcher) .after("after_state_applied_on_1"); Future<Object> txFuture = fork(() -> { tm(0).begin(); try { cache(1).put(k1, "v1_1"); cache(1).put(k2, "v2_1"); } finally { tm(0).commit(); } return null; }); ss.advance("before_kill_3"); controlledCHFactory.setOwnerIndexes(new int[]{1, 2}, new int[]{2, 1}); manager(3).stop(); cacheManagers.remove(3); txFuture.get(30, TimeUnit.SECONDS); assertEquals("v1_1", cache(1).get(k1)); assertEquals("v2_1", cache(1).get(k2)); assertEquals("v1_1", cache(2).get(k1)); assertEquals("v2_1", cache(2).get(k2)); } }