/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ignite.internal.processors.cache.distributed.near; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.transactions.TransactionProxyImpl; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.apache.ignite.transactions.Transaction; import org.apache.ignite.transactions.TransactionConcurrency; import static org.apache.ignite.IgniteSystemProperties.IGNITE_TX_SALVAGE_TIMEOUT; import static org.apache.ignite.cache.CacheMode.PARTITIONED; import static org.apache.ignite.cache.CacheRebalanceMode.SYNC; import static org.apache.ignite.transactions.TransactionConcurrency.OPTIMISTIC; import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC; import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ; /** * Test tx salvage. */ public class GridCachePartitionedTxSalvageSelfTest extends GridCommonAbstractTest { /** Grid count. */ private static final int GRID_CNT = 5; /** Key count. */ private static final int KEY_CNT = 10; /** Salvage timeout system property value. */ private static final Integer SALVAGE_TIMEOUT = 5000; /** Difference between salvage timeout and actual wait time when performing "before salvage" tests. */ private static final int DELTA_BEFORE = 1000; /** How much time to wait after salvage timeout when performing "after salvage" tests. */ private static final int DELTA_AFTER = 1000; /** Salvage timeout system property value before alteration. */ private static String salvageTimeoutOld; /** Standard VM IP finder. */ private static final TcpDiscoveryIpFinder ipFinder = new TcpDiscoveryVmIpFinder(true); /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration c = super.getConfiguration(igniteInstanceName); // Discovery. TcpDiscoverySpi disco = new TcpDiscoverySpi(); disco.setIpFinder(ipFinder); c.setDiscoverySpi(disco); CacheConfiguration cc = defaultCacheConfiguration(); cc.setCacheMode(PARTITIONED); cc.setAffinity(new RendezvousAffinityFunction(false, 18)); cc.setBackups(1); cc.setRebalanceMode(SYNC); c.setCacheConfiguration(cc); return c; } /** {@inheritDoc} */ @Override protected void beforeTestsStarted() throws Exception { // Set salvage timeout system property. salvageTimeoutOld = System.setProperty(IGNITE_TX_SALVAGE_TIMEOUT, SALVAGE_TIMEOUT.toString()); } /** {@inheritDoc} */ @Override protected void afterTestsStopped() throws Exception { // Restore salvage timeout system property to its initial state. if (salvageTimeoutOld != null) System.setProperty(IGNITE_TX_SALVAGE_TIMEOUT, salvageTimeoutOld); else System.clearProperty(IGNITE_TX_SALVAGE_TIMEOUT); } /** {@inheritDoc} */ @Override protected void beforeTest() throws Exception { // Start the grid. startGridsMultiThreaded(GRID_CNT); } /** {@inheritDoc} */ @Override protected void afterTest() throws Exception { stopAllGrids(); System.gc(); } /** * @throws Exception If failed. */ public void testOptimisticTxSalvageBeforeTimeout() throws Exception { checkSalvageBeforeTimeout(OPTIMISTIC, true); } /** * @throws Exception If failed. */ public void testPessimisticcTxSalvageBeforeTimeout() throws Exception { checkSalvageBeforeTimeout(PESSIMISTIC, false); } /** * @throws Exception If failed. */ public void testOptimisticTxSalvageAfterTimeout() throws Exception { checkSalvageAfterTimeout(OPTIMISTIC, true); } /** * @throws Exception If failed. */ public void testPessimisticTxSalvageAfterTimeout() throws Exception { checkSalvageAfterTimeout(PESSIMISTIC, false); } /** * Check whether caches has no transactions after salvage timeout. * * @param mode Transaction mode (PESSIMISTIC, OPTIMISTIC). * @param prepare Whether to prepare transaction state (i.e. call {@link GridNearTxLocal#prepare()}). * @throws Exception If failed. */ private void checkSalvageAfterTimeout(TransactionConcurrency mode, boolean prepare) throws Exception { startTxAndPutKeys(mode, prepare); stopNodeAndSleep(SALVAGE_TIMEOUT + DELTA_AFTER); for (int i = 1; i < GRID_CNT; i++) { checkTxsEmpty(near(i).context()); checkTxsEmpty(dht(i).context()); } } /** * Check whether caches still has all transactions before salvage timeout. * * @param mode Transaction mode (PESSIMISTIC, OPTIMISTIC). * @param prepare Whether to prepare transaction state * (i.e. call {@link GridNearTxLocal#prepare()}). * @throws Exception If failed. */ private void checkSalvageBeforeTimeout(TransactionConcurrency mode, boolean prepare) throws Exception { startTxAndPutKeys(mode, prepare); List<Integer> nearSizes = new ArrayList<>(GRID_CNT - 1); List<Integer> dhtSizes = new ArrayList<>(GRID_CNT - 1); for (int i = 1; i < GRID_CNT; i++) { nearSizes.add(near(i).context().tm().txs().size()); dhtSizes.add(dht(i).context().tm().txs().size()); } stopNodeAndSleep(SALVAGE_TIMEOUT - DELTA_BEFORE); for (int i = 1; i < GRID_CNT; i++) { checkTxsNotEmpty(near(i).context(), nearSizes.get(i - 1)); checkTxsNotEmpty(dht(i).context(), dhtSizes.get(i - 1)); } } /** * Start new transaction on the grid(0) and put some keys to it. * * @param mode Transaction mode (PESSIMISTIC, OPTIMISTIC). * @param prepare Whether to prepare transaction state (i.e. call {@link GridNearTxLocal#prepare()}). * @throws Exception If failed. */ private void startTxAndPutKeys(final TransactionConcurrency mode, final boolean prepare) throws Exception { Ignite ignite = grid(0); final Collection<Integer> keys = nearKeys(ignite.cache(DEFAULT_CACHE_NAME), KEY_CNT, 0); IgniteInternalFuture<?> fut = multithreadedAsync(new Runnable() { @Override public void run() { IgniteCache<Object, Object> c = jcache(0); try { Transaction tx = grid(0).transactions().txStart(mode, REPEATABLE_READ); for (Integer key : keys) c.put(key, "val" + key); if (prepare) ((TransactionProxyImpl)tx).tx().prepare(); } catch (IgniteCheckedException e) { info("Failed to put keys to cache: " + e.getMessage()); } } }, 1); fut.get(); } /** * Stop the very first grid node (the one with 0 index) and sleep for the given amount of time. * * @param timeout Sleep timeout in milliseconds. * @throws Exception If failed. */ private void stopNodeAndSleep(long timeout) throws Exception { stopGrid(getTestIgniteInstanceName(0), false, false); info("Stopped grid."); U.sleep(timeout); } /** * Checks that transaction manager for cache context does not have any pending transactions. * * @param ctx Cache context. */ private void checkTxsEmpty(GridCacheContext ctx) { Collection txs = ctx.tm().txs(); assert txs.isEmpty() : "Not all transactions were salvaged: " + txs; } /** * Checks that transaction manager for cache context has expected number of pending transactions. * * @param ctx Cache context. * @param exp Expected amount of transactions. */ private void checkTxsNotEmpty(GridCacheContext ctx, int exp) { int size = ctx.tm().txs().size(); assertEquals("Some transactions were salvaged unexpectedly", exp, size); } }