/* * Copyright © 2012-2014 Cask Data, Inc. * * Licensed 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 co.cask.tephra; import co.cask.tephra.inmemory.InMemoryTxSystemClient; import co.cask.tephra.metrics.TxMetricsCollector; import co.cask.tephra.persist.InMemoryTransactionStateStorage; import co.cask.tephra.persist.TransactionStateStorage; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import org.apache.hadoop.conf.Configuration; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.Collections; import java.util.concurrent.TimeUnit; /** * */ public class TransactionManagerTest extends TransactionSystemTest { static Configuration conf = new Configuration(); TransactionManager txManager = null; TransactionStateStorage txStateStorage = null; @Override protected TransactionSystemClient getClient() { return new InMemoryTxSystemClient(txManager); } @Override protected TransactionStateStorage getStateStorage() { return txStateStorage; } @Before public void before() { conf.setInt(TxConstants.Manager.CFG_TX_CLEANUP_INTERVAL, 0); // no cleanup thread // todo should create two sets of tests, one with LocalFileTxStateStorage and one with InMemoryTxStateStorage txStateStorage = new InMemoryTransactionStateStorage(); txManager = new TransactionManager (conf, txStateStorage, new TxMetricsCollector()); txManager.startAndWait(); } @After public void after() { txManager.stopAndWait(); } @Test public void testTransactionCleanup() throws Exception { conf.setInt(TxConstants.Manager.CFG_TX_CLEANUP_INTERVAL, 3); conf.setInt(TxConstants.Manager.CFG_TX_TIMEOUT, 2); // using a new tx manager that cleans up TransactionManager txm = new TransactionManager (conf, new InMemoryTransactionStateStorage(), new TxMetricsCollector()); txm.startAndWait(); try { Assert.assertEquals(0, txm.getInvalidSize()); Assert.assertEquals(0, txm.getCommittedSize()); // start a transaction and leave it open Transaction tx1 = txm.startShort(); // start a long running transaction and leave it open Transaction tx2 = txm.startLong(); Transaction tx3 = txm.startLong(); // start and commit a bunch of transactions for (int i = 0; i < 10; i++) { Transaction tx = txm.startShort(); Assert.assertTrue(txm.canCommit(tx, Collections.singleton(new byte[] { (byte) i }))); Assert.assertTrue(txm.commit(tx)); } // all of these should still be in the committed set Assert.assertEquals(0, txm.getInvalidSize()); Assert.assertEquals(10, txm.getCommittedSize()); // sleep longer than the cleanup interval TimeUnit.SECONDS.sleep(5); // transaction should now be invalid Assert.assertEquals(1, txm.getInvalidSize()); // run another transaction Transaction txx = txm.startShort(); // verify the exclude Assert.assertFalse(txx.isVisible(tx1.getTransactionId())); Assert.assertFalse(txx.isVisible(tx2.getTransactionId())); Assert.assertFalse(txx.isVisible(tx3.getTransactionId())); // try to commit the last transaction that was started Assert.assertTrue(txm.canCommit(txx, Collections.singleton(new byte[] { 0x0a }))); Assert.assertTrue(txm.commit(txx)); // now the committed change sets should be empty again Assert.assertEquals(0, txm.getCommittedSize()); // cannot commit transaction as it was timed out try { txm.canCommit(tx1, Collections.singleton(new byte[] { 0x11 })); Assert.fail(); } catch (TransactionNotInProgressException e) { // expected } txm.abort(tx1); // abort should have removed from invalid Assert.assertEquals(0, txm.getInvalidSize()); // run another bunch of transactions for (int i = 0; i < 10; i++) { Transaction tx = txm.startShort(); Assert.assertTrue(txm.canCommit(tx, Collections.singleton(new byte[] { (byte) i }))); Assert.assertTrue(txm.commit(tx)); } // none of these should still be in the committed set (tx2 is long-running). Assert.assertEquals(0, txm.getInvalidSize()); Assert.assertEquals(0, txm.getCommittedSize()); // commit tx2, abort tx3 Assert.assertTrue(txm.commit(tx2)); txm.abort(tx3); // none of these should still be in the committed set (tx2 is long-running). // Only tx3 is invalid list as it was aborted and is long-running. tx1 is short one and it rolled back its changes // so it should NOT be in invalid list Assert.assertEquals(1, txm.getInvalidSize()); Assert.assertEquals(tx3.getTransactionId(), (long) txm.getCurrentState().getInvalid().iterator().next()); Assert.assertEquals(1, txm.getExcludedListSize()); } finally { txm.stopAndWait(); } } @Test public void testLongTransactionCleanup() throws Exception { conf.setInt(TxConstants.Manager.CFG_TX_CLEANUP_INTERVAL, 3); conf.setInt(TxConstants.Manager.CFG_TX_LONG_TIMEOUT, 2); // using a new tx manager that cleans up TransactionManager txm = new TransactionManager (conf, new InMemoryTransactionStateStorage(), new TxMetricsCollector()); txm.startAndWait(); try { Assert.assertEquals(0, txm.getInvalidSize()); Assert.assertEquals(0, txm.getCommittedSize()); // start a long running transaction Transaction tx1 = txm.startLong(); Assert.assertEquals(0, txm.getInvalidSize()); Assert.assertEquals(0, txm.getCommittedSize()); // sleep longer than the cleanup interval TimeUnit.SECONDS.sleep(5); // transaction should now be invalid Assert.assertEquals(1, txm.getInvalidSize()); Assert.assertEquals(0, txm.getCommittedSize()); // cannot commit transaction as it was timed out try { txm.canCommit(tx1, Collections.singleton(new byte[] { 0x11 })); Assert.fail(); } catch (TransactionNotInProgressException e) { // expected } txm.abort(tx1); // abort should not remove long running transaction from invalid list Assert.assertEquals(1, txm.getInvalidSize()); } finally { txm.stopAndWait(); } } @Test public void testTruncateInvalid() throws Exception { InMemoryTransactionStateStorage storage = new InMemoryTransactionStateStorage(); Configuration testConf = new Configuration(conf); // No snapshots testConf.setLong(TxConstants.Manager.CFG_TX_SNAPSHOT_INTERVAL, -1); TransactionManager txm1 = new TransactionManager(testConf, storage, new TxMetricsCollector()); txm1.startAndWait(); TransactionManager txm2 = null; Transaction tx1; Transaction tx2; Transaction tx3; Transaction tx4; Transaction tx5; Transaction tx6; try { Assert.assertEquals(0, txm1.getInvalidSize()); // start a few transactions tx1 = txm1.startLong(); tx2 = txm1.startShort(); tx3 = txm1.startLong(); tx4 = txm1.startShort(); tx5 = txm1.startLong(); tx6 = txm1.startShort(); // invalidate tx1, tx2, tx5 and tx6 txm1.invalidate(tx1.getTransactionId()); txm1.invalidate(tx2.getTransactionId()); txm1.invalidate(tx5.getTransactionId()); txm1.invalidate(tx6.getTransactionId()); // tx1, tx2, tx5 and tx6 should be in invalid list Assert.assertEquals( ImmutableList.of(tx1.getTransactionId(), tx2.getTransactionId(), tx5.getTransactionId(), tx6.getTransactionId()), txm1.getCurrentState().getInvalid() ); // remove tx1 and tx6 from invalid list Assert.assertTrue(txm1.truncateInvalidTx(ImmutableSet.of(tx1.getTransactionId(), tx6.getTransactionId()))); // only tx2 and tx5 should be in invalid list now Assert.assertEquals(ImmutableList.of(tx2.getTransactionId(), tx5.getTransactionId()), txm1.getCurrentState().getInvalid()); // removing in-progress transactions should not have any effect Assert.assertEquals(ImmutableSet.of(tx3.getTransactionId(), tx4.getTransactionId()), txm1.getCurrentState().getInProgress().keySet()); Assert.assertFalse(txm1.truncateInvalidTx(ImmutableSet.of(tx3.getTransactionId(), tx4.getTransactionId()))); // no change to in-progress Assert.assertEquals(ImmutableSet.of(tx3.getTransactionId(), tx4.getTransactionId()), txm1.getCurrentState().getInProgress().keySet()); // no change to invalid list Assert.assertEquals(ImmutableList.of(tx2.getTransactionId(), tx5.getTransactionId()), txm1.getCurrentState().getInvalid()); // Test transaction edit logs replay // Start another transaction manager without stopping txm1 so that snapshot does not get written, // and all logs can be replayed. txm2 = new TransactionManager(testConf, storage, new TxMetricsCollector()); txm2.startAndWait(); Assert.assertEquals(ImmutableList.of(tx2.getTransactionId(), tx5.getTransactionId()), txm2.getCurrentState().getInvalid()); Assert.assertEquals(ImmutableSet.of(tx3.getTransactionId(), tx4.getTransactionId()), txm2.getCurrentState().getInProgress().keySet()); } finally { txm1.stopAndWait(); if (txm2 != null) { txm2.stopAndWait(); } } } @Test public void testTruncateInvalidBeforeTime() throws Exception { InMemoryTransactionStateStorage storage = new InMemoryTransactionStateStorage(); Configuration testConf = new Configuration(conf); // No snapshots testConf.setLong(TxConstants.Manager.CFG_TX_SNAPSHOT_INTERVAL, -1); TransactionManager txm1 = new TransactionManager(testConf, storage, new TxMetricsCollector()); txm1.startAndWait(); TransactionManager txm2 = null; Transaction tx1; Transaction tx2; Transaction tx3; Transaction tx4; Transaction tx5; Transaction tx6; try { Assert.assertEquals(0, txm1.getInvalidSize()); // start a few transactions tx1 = txm1.startLong(); tx2 = txm1.startShort(); // Sleep so that transaction ids get generated a millisecond apart for assertion // TEPHRA-63 should eliminate the need to sleep TimeUnit.MILLISECONDS.sleep(1); long timeBeforeTx3 = System.currentTimeMillis(); tx3 = txm1.startLong(); tx4 = txm1.startShort(); TimeUnit.MILLISECONDS.sleep(1); long timeBeforeTx5 = System.currentTimeMillis(); tx5 = txm1.startLong(); tx6 = txm1.startShort(); // invalidate tx1, tx2, tx5 and tx6 txm1.invalidate(tx1.getTransactionId()); txm1.invalidate(tx2.getTransactionId()); txm1.invalidate(tx5.getTransactionId()); txm1.invalidate(tx6.getTransactionId()); // tx1, tx2, tx5 and tx6 should be in invalid list Assert.assertEquals( ImmutableList.of(tx1.getTransactionId(), tx2.getTransactionId(), tx5.getTransactionId(), tx6.getTransactionId()), txm1.getCurrentState().getInvalid() ); // remove transactions before tx3 from invalid list Assert.assertTrue(txm1.truncateInvalidTxBefore(timeBeforeTx3)); // only tx5 and tx6 should be in invalid list now Assert.assertEquals(ImmutableList.of(tx5.getTransactionId(), tx6.getTransactionId()), txm1.getCurrentState().getInvalid()); // removing invalid transactions before tx5 should throw exception since tx3 and tx4 are in-progress Assert.assertEquals(ImmutableSet.of(tx3.getTransactionId(), tx4.getTransactionId()), txm1.getCurrentState().getInProgress().keySet()); try { txm1.truncateInvalidTxBefore(timeBeforeTx5); Assert.fail("Expected InvalidTruncateTimeException exception"); } catch (InvalidTruncateTimeException e) { // Expected exception } // no change to in-progress Assert.assertEquals(ImmutableSet.of(tx3.getTransactionId(), tx4.getTransactionId()), txm1.getCurrentState().getInProgress().keySet()); // no change to invalid list Assert.assertEquals(ImmutableList.of(tx5.getTransactionId(), tx6.getTransactionId()), txm1.getCurrentState().getInvalid()); // Test transaction edit logs replay // Start another transaction manager without stopping txm1 so that snapshot does not get written, // and all logs can be replayed. txm2 = new TransactionManager(testConf, storage, new TxMetricsCollector()); txm2.startAndWait(); Assert.assertEquals(ImmutableList.of(tx5.getTransactionId(), tx6.getTransactionId()), txm2.getCurrentState().getInvalid()); Assert.assertEquals(ImmutableSet.of(tx3.getTransactionId(), tx4.getTransactionId()), txm2.getCurrentState().getInProgress().keySet()); } finally { txm1.stopAndWait(); if (txm2 != null) { txm2.stopAndWait(); } } } }