/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Dec 23, 2008 */ package com.bigdata.service; import java.io.File; import java.util.Properties; import junit.framework.TestCase2; import com.bigdata.btree.ITuple; import com.bigdata.btree.ITupleIterator; /** * Unit tests of the setReleaseTime, snapshot and restart aspects of the * {@link DistributedTransactionService} (all having to do with the maintenance * of the commit time index, including across restart). * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class TestDistributedTransactionServiceRestart extends TestCase2 { /** * */ public TestDistributedTransactionServiceRestart() { } /** * @param arg0 */ public TestDistributedTransactionServiceRestart(String arg0) { super(arg0); } protected static class MockDistributedTransactionService extends DistributedTransactionService { public MockDistributedTransactionService( Properties properties) { super(properties); } @Override public AbstractFederation getFederation() { throw new UnsupportedOperationException(); } @Override protected void setReleaseTime(final long releaseTime) { lock.lock(); try { super.setReleaseTime(releaseTime); } finally { lock.unlock(); } } /** * Note: The scheduled tasks are disabled for the unit test since we do * not have a federation for this test. */ @Override protected void addScheduledTasks() { // NOP. } @Override public MockDistributedTransactionService start() { super.start(); return this; } /** * This is overridden to be a NOP for this test suite. The unit tests in * this suite depend on the ability to inject specific commit times into * the transaction service without having them "release" based on the * actual system clock. */ @Override protected void updateReleaseTimeForBareCommit(final long commitTime) { return; } /** * Exposed to the unit tests. */ CommitTimeIndex getCommitTimeIndex() { return commitTimeIndex; } } /** * Return an array containing the ordered keys in the * {@link CommitTimeIndex}. * * @param ndx * * @return The array. */ long[] toArray(final CommitTimeIndex ndx) { synchronized(ndx) { if (ndx.getEntryCount() > Integer.MAX_VALUE) { fail("Test case can not handle massive indices"); } final long[] a = new long[(int)ndx.getEntryCount()]; final ITupleIterator<?> itr = ndx.rangeIterator(); int i = 0; while(itr.hasNext()) { final ITuple<?> tuple = itr.next(); a[i] = ndx.decodeKey(tuple.getKey()); i++; } return a; } } /** * Unit tests verifies that the head of the commit time index is truncated * when the release time is advanced and that it is still possible to obtain * a read-only tx as of the timestamp immediately after the current release * time. */ public void test_setReleaseTime() { final Properties properties = new Properties(); properties.setProperty(DistributedTransactionService.Options.DATA_DIR, getName()); properties.setProperty(DistributedTransactionService.Options.MIN_RELEASE_AGE, "10"); final MockDistributedTransactionService service = new MockDistributedTransactionService( properties).start(); try { /* * populate the commit index. */ service.notifyCommit(10L); service.notifyCommit(20L); // verify the commit index. { final CommitTimeIndex ndx = service.getCommitTimeIndex(); synchronized (ndx) { assertEquals(2, ndx.getEntryCount()); assertEquals(new long[] { 10, 20 }, toArray(ndx)); } } // verify that we can obtain a read-only tx. service.abort(service.newTx(10L)); // verify that we can obtain a read-only tx. service.abort(service.newTx(20L)); service.setReleaseTime(20L - 1); { final CommitTimeIndex ndx = service.getCommitTimeIndex(); synchronized (ndx) { assertEquals(1, ndx.getEntryCount()); assertEquals(new long[] { 20 }, toArray(ndx)); } } // verify that we can still obtain a read-only tx for that commit time. service.abort(service.newTx(20L)); /* * Verify that we can not release the lastCommitTime. */ assertEquals(20L,service.getLastCommitTime()); service.setReleaseTime(20L); { final CommitTimeIndex ndx = service.getCommitTimeIndex(); synchronized (ndx) { assertEquals(1, ndx.getEntryCount()); assertEquals(new long[] { 20 }, toArray(ndx)); } } } finally { service.destroy(); } } /** * Unit test of the ability to snapshot the commit index. The test setups up * the expected state of the commit index, verifies that state, shuts down * the service, and verifies that a snapshot was written during shutdown. * The test then restarts the service, and verifies that the commit index * has the expected values (read from the snapshot). Another entry is then * added to the commit index and the release time is advanced, which should * cause the head of the commit index to be truncated. The test then * verifies the expected state of the commit index, shuts down the service, * and verifies that another snapshot file was written. Finally, we restart * the service one more time and verify that the commit index has the data * that we would expect if it had read the 2nd snapshot. */ public void test_snapshotCommitIndex() { final Properties properties = new Properties(); properties.setProperty(DistributedTransactionService.Options.DATA_DIR, getName()); MockDistributedTransactionService service = new MockDistributedTransactionService( properties).start(); final File file0 = new File(service.dataDir, DistributedTransactionService.BASENAME + "0" + DistributedTransactionService.EXT); final File file1 = new File(service.dataDir, DistributedTransactionService.BASENAME + "1" + DistributedTransactionService.EXT); try { /* * populate the commit log. */ service.notifyCommit(10L); service.notifyCommit(20L); { final CommitTimeIndex ndx = service.getCommitTimeIndex(); synchronized(ndx) { assertEquals(2, ndx.getEntryCount()); assertEquals(new long[] { 10, 20 }, toArray(ndx)); } } // should do a snapshot. service.shutdown(); assertTrue(file0.exists()); assertFalse(file1.exists()); // restart the service. service = new MockDistributedTransactionService(properties).start(); // verify the commit time index. { final CommitTimeIndex ndx = service.getCommitTimeIndex(); synchronized(ndx) { assertEquals(2, ndx.getEntryCount()); assertEquals(new long[] { 10, 20 }, toArray(ndx)); } } service.setReleaseTime(20L - 1); { final CommitTimeIndex ndx = service.getCommitTimeIndex(); synchronized (ndx) { assertEquals(1, ndx.getEntryCount()); assertEquals(new long[] { 20 }, toArray(ndx)); } } // should do a snapshot. service.shutdown(); // System.err.println("file0: "+file0.lastModified()); // System.err.println("file1: "+file1.lastModified()); assertTrue(file0.exists()); assertTrue(file1.exists()); // restart the service. service = new MockDistributedTransactionService(properties).start(); // verify the commit time index. { final CommitTimeIndex ndx = service.getCommitTimeIndex(); synchronized(ndx) { assertEquals(1, ndx.getEntryCount()); assertEquals(new long[] { 20 }, toArray(ndx)); } } } finally { service.destroy(); } } }