/* * INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa * Copyright 2013 INESC-ID and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3.0 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.tx.gmu; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.testng.annotations.Test; import javax.transaction.Transaction; import java.util.Arrays; /** * // TODO: Document this * * @author Pedro Ruivo * @since 5.2 */ @Test(groups = "functional", testName = "tx.gmu.DistConsistencyTest3") public class DistConsistencyTest3 extends AbstractGMUTest { public DistConsistencyTest3() { cleanup = CleanupPhase.AFTER_METHOD; } public void testConcurrentTransactionConsistency() throws Exception { assertAtLeastCaches(3); rewireMagicKeyAwareConsistentHash(); final int initialValue = 1000; final int numberOfKeys = 6; final Object cache0Key0 = newKey(0, Arrays.asList(1, 2)); final Object cache0Key1 = newKey(0, Arrays.asList(1,2)); final Object cache1Key0 = newKey(1, Arrays.asList(0,2)); final Object cache1Key1 = newKey(1, Arrays.asList(0,2)); final Object cache2Key0 = newKey(2, Arrays.asList(1,0)); final Object cache2Key1 = newKey(2, Arrays.asList(1,0)); logKeysUsedInTest("testConcurrentTransactionConsistency", cache0Key0, cache0Key1, cache1Key0, cache1Key1, cache2Key0, cache2Key1); assertKeyOwners(cache0Key0, Arrays.asList(0), Arrays.asList(1, 2)); assertKeyOwners(cache0Key1, Arrays.asList(0), Arrays.asList(1, 2)); assertKeyOwners(cache1Key0, Arrays.asList(1), Arrays.asList(0, 2)); assertKeyOwners(cache1Key1, Arrays.asList(1), Arrays.asList(0, 2)); assertKeyOwners(cache2Key0, Arrays.asList(2), Arrays.asList(1, 0)); assertKeyOwners(cache2Key1, Arrays.asList(2), Arrays.asList(1, 0)); final DelayCommit cache1DelayCommit = addDelayCommit(1, -1); final DelayCommit cache2DelayCommit = addDelayCommit(2, -1); //init all keys with value_1 tm(0).begin(); txPut(0, cache0Key0, initialValue, null); txPut(0, cache0Key1, initialValue, null); txPut(0, cache1Key0, initialValue, null); txPut(0, cache1Key1, initialValue, null); txPut(0, cache2Key0, initialValue, null); txPut(0, cache2Key1, initialValue, null); tm(0).commit(); //one transaction commits in cache 0 (key0 - 100, key1 + 100) //global sum is the same tm(0).begin(); int value = (Integer) cache(0).get(cache0Key0); txPut(0, cache0Key0, value - 100, value); value = (Integer) cache(0).get(cache0Key1); txPut(0, cache0Key1, value + 100, value); tm(0).commit(); int[] allValues = new int[numberOfKeys]; //read only transaction starts tm(0).begin(); allValues[0] = (Integer) cache(0).get(cache0Key0); Transaction readOnlyTx = tm(0).suspend(); //first concurrent transaction starts and prepares in cache 0 and cache 2 (coord) tm(2).begin(); value = (Integer) cache(2).get(cache2Key0); txPut(2, cache2Key0, value - 200, value); value = (Integer) cache(2).get(cache0Key0); txPut(2, cache0Key0, value + 200, value); Transaction concurrentTx1 = tm(2).suspend(); Thread threadTx1 = prepareInAllNodes(concurrentTx1, cache2DelayCommit, 2); //second concurrent transaction stats and prepares in cache 1 (coord) and cache 2 tm(1).begin(); value = (Integer) cache(1).get(cache1Key1); txPut(1, cache1Key1, value - 300, value); value = (Integer) cache(1).get(cache2Key1); txPut(1, cache2Key1, value + 300, value); Transaction concurrentTx2 = tm(1).suspend(); Thread threadTx2 = prepareInAllNodes(concurrentTx2, cache1DelayCommit, 1); //all transactions are prepared. Commit first transaction first, and then the second one cache2DelayCommit.unblock(); cache1DelayCommit.unblock(); threadTx1.join(); threadTx2.join(); //all transactions are committed. Check if the read only can read a consistent snapshot tm(0).resume(readOnlyTx); allValues[1] = (Integer) cache(0).get(cache1Key1); allValues[2] = (Integer) cache(0).get(cache0Key1); allValues[3] = (Integer) cache(0).get(cache1Key0); allValues[4] = (Integer) cache(0).get(cache2Key1); allValues[5] = (Integer) cache(0).get(cache2Key0); tm(0).commit(); /* cache0Key0->1000(v1)->900(v2*) ->700(v3:1*) cache0Key1->1000(v1)->1100(v2*) cache1Key0->1000(v1*) cache1Key1->1000(v1)->700(v3:0*) cache2Key0->1000(v1*)->1200(v3:1) cache2Key1->1000(v1)->1300(v3:0*) the * represents the correct versions to read */ int sum = 0; for (int v : allValues) { sum += v; } assert sum == (initialValue * numberOfKeys) : "Read an inconsistent snapshot"; printDataContainer(); assertNoTransactions(); } public void testConcurrentTransactionConsistency2() throws Exception { assertAtLeastCaches(3); rewireMagicKeyAwareConsistentHash(); final int initialValue = 1000; final int numberOfKeys = 4; final Object cache0Key0 = newKey(0, Arrays.asList(1,2)); final Object cache0Key1 = newKey(0, Arrays.asList(1,2)); final Object cache1Key0 = newKey(1, Arrays.asList(0,2)); final Object cache2Key0 = newKey(2, Arrays.asList(1,0)); logKeysUsedInTest("testConcurrentTransactionConsistency", cache0Key0, cache0Key1, cache1Key0, cache2Key0); assertKeyOwners(cache0Key0, Arrays.asList(0), Arrays.asList(1,2)); assertKeyOwners(cache0Key1, Arrays.asList(0), Arrays.asList(1,2)); assertKeyOwners(cache1Key0, Arrays.asList(1), Arrays.asList(0,2)); assertKeyOwners(cache2Key0, Arrays.asList(2), Arrays.asList(1,0)); final DelayCommit cache1DelayCommit = addDelayCommit(1, -1); final DelayCommit cache2DelayCommit = addDelayCommit(2, -1); //init all keys with value_1 tm(0).begin(); txPut(0, cache0Key0, initialValue, null); txPut(0, cache0Key1, initialValue, null); txPut(0, cache1Key0, initialValue, null); txPut(0, cache2Key0, initialValue, null); tm(0).commit(); int[] allValues = new int[numberOfKeys]; //read only transaction starts tm(1).begin(); allValues[0] = (Integer) cache(1).get(cache2Key0); //vector [-,-,1] Transaction readOnlyTx = tm(1).suspend(); //first concurrent transaction starts and prepares in cache 0 and cache 2 (coord) tm(2).begin(); int value = (Integer) cache(2).get(cache2Key0); txPut(2, cache2Key0, value - 200, value); value = (Integer) cache(2).get(cache0Key0); txPut(2, cache0Key0, value + 200, value); Transaction concurrentTx1 = tm(2).suspend(); //transaction is prepared with [2,1,1] and [1,1,2] Thread threadTx1 = prepareInAllNodes(concurrentTx1, cache2DelayCommit, 2); //second concurrent transaction stats and prepares in cache 1 (coord) and cache 0 tm(1).begin(); value = (Integer) cache(1).get(cache1Key0); txPut(1, cache1Key0, value - 300, value); value = (Integer) cache(1).get(cache0Key1); txPut(1, cache0Key1, value + 300, value); Transaction concurrentTx2 = tm(1).suspend(); //transaction is prepared with [3,1,1 and [1,2,1] Thread threadTx2 = prepareInAllNodes(concurrentTx2, cache1DelayCommit, 1); //all transactions are prepared. Commit first transaction first, and then the second one cache2DelayCommit.unblock(); cache1DelayCommit.unblock(); threadTx1.join(); threadTx2.join(); //all transactions are committed. Check if the read only can read a consistent snapshot tm(1).resume(readOnlyTx); //read first local key to update transaction version to [3,3,1] allValues[1] = (Integer) cache(1).get(cache1Key0); allValues[2] = (Integer) cache(1).get(cache0Key0); allValues[3] = (Integer) cache(1).get(cache0Key1); tm(1).commit(); /* cache0Key0->1000(v1*)->1200(v2) cache0Key1->1000(v1) ->1300(v3*) cache1Key0->1000(v1) ->700(v3*) cache2Key0->1000(v1*)->800(v2) the * represents the correct versions to read */ int sum = 0; for (int v : allValues) { sum += v; } assert sum == (initialValue * numberOfKeys) : "Read an inconsistent snapshot"; printDataContainer(); assertNoTransactions(); } public void testWriteConsistency() throws Exception{ assertAtLeastCaches(2); rewireMagicKeyAwareConsistentHash(); final int initialValue = 1000; final int numberOfKeys = 5; final Object cache0Key0 = newKey(0, Arrays.asList(1)); final Object cache0Key1 = newKey(0, Arrays.asList(1)); final Object cache0Key2 = newKey(0, Arrays.asList(1)); final Object cache1Key0 = newKey(1, Arrays.asList(0)); final Object cache1Key1 = newKey(1, Arrays.asList(0)); logKeysUsedInTest("testConcurrentTransactionConsistency", cache0Key0, cache0Key1, cache0Key2, cache1Key0, cache1Key1); assertKeyOwners(cache0Key0, Arrays.asList(0), Arrays.asList(1)); assertKeyOwners(cache0Key1, Arrays.asList(0), Arrays.asList(1)); assertKeyOwners(cache0Key2, Arrays.asList(0), Arrays.asList(1)); assertKeyOwners(cache1Key0, Arrays.asList(1), Arrays.asList(0)); assertKeyOwners(cache1Key1, Arrays.asList(1), Arrays.asList(0)); final DelayCommit cache0DelayCommit = addDelayCommit(0, -1); final DelayCommit cache1DelayCommit = addDelayCommit(1, -1); //init all keys with value_1 tm(0).begin(); txPut(0, cache0Key0, initialValue, null); txPut(0, cache0Key1, initialValue, null); txPut(0, cache0Key2, initialValue, null); txPut(0, cache1Key0, initialValue, null); txPut(0, cache1Key1, initialValue, null); tm(0).commit(); //first concurrent transaction starts and prepares in cache 0 (coord) and cache 1 tm(1).begin(); int value = (Integer) cache(1).get(cache0Key0); txPut(1, cache0Key0, value - 200, value); value = (Integer) cache(1).get(cache1Key0); txPut(1, cache1Key0, value + 200, value); Transaction concurrentTx1 = tm(1).suspend(); //transaction is prepared with [2,1,1] and [1,1,2] Thread threadTx1 = prepareInAllNodes(concurrentTx1, cache1DelayCommit, 1); //second concurrent transaction stats and prepares in cache 0 (coord) and cache 1 tm(0).begin(); value = (Integer) cache(0).get(cache0Key1); txPut(0, cache0Key1, value - 300, value); value = (Integer) cache(0).get(cache0Key2); txPut(0, cache0Key2, value + 300, value); Transaction concurrentTx2 = tm(0).suspend(); Thread threadTx2 = prepareInAllNodes(concurrentTx2, cache0DelayCommit, 0); //all transactions are prepared. Commit first transaction first, and then the second one cache0DelayCommit.unblock(); cache1DelayCommit.unblock(); threadTx1.join(); threadTx2.join(); //Problem: sometimes, the transaction reads an old version and does not aborts tm(0).begin(); value = (Integer) cache(0).get(cache0Key0); txPut(0, cache0Key0, value - 500, value); value = (Integer) cache(0).get(cache1Key1); txPut(0, cache1Key1, value + 500, value); tm(0).commit(); int[] allValues = new int[numberOfKeys]; tm(0).begin(); allValues[0] = (Integer) cache(0).get(cache0Key0); allValues[1] = (Integer) cache(0).get(cache0Key1); allValues[2] = (Integer) cache(0).get(cache0Key2); allValues[3] = (Integer) cache(0).get(cache1Key0); allValues[4] = (Integer) cache(0).get(cache1Key1); tm(0).commit(); int sum = 0; for (int v : allValues) { sum += v; } assert sum == (initialValue * numberOfKeys) : "Read an inconsistent snapshot"; printDataContainer(); assertNoTransactions(); } @Override protected void decorate(ConfigurationBuilder builder) { builder.clustering().clustering().hash().numOwners(1); } @Override protected int initialClusterSize() { return 3; } @Override protected boolean syncCommitPhase() { return true; } @Override protected CacheMode cacheMode() { return CacheMode.DIST_SYNC; } }