package com.bigdata.journal;
import java.io.File;
import java.io.IOException;
import java.util.Properties;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.TestCase2;
import com.bigdata.btree.BTree;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.journal.Journal.Options;
import com.bigdata.rwstore.RWStore;
import com.bigdata.util.Bytes;
import com.bigdata.util.InnerCause;
/**
* Test suite for a failure to handle errors inside of abort() by marking the
* journal as requiring abort().
*
* @see #1021 (Add critical section protection to AbstractJournal.abort() and
* BigdataSailConnection.rollback())
*
* @author martyncutcher
*
* TODO Thia should be a proxied test suite. It is RWStore specific.
*/
public class TestJournalAbort extends TestCase2 {
/**
*
*/
public TestJournalAbort() {
}
/**
* @param name
*/
public TestJournalAbort(String name) {
super(name);
}
@Override
public void setUp() throws Exception {
super.setUp();
}
@Override
public void tearDown() throws Exception {
TestHelper.checkJournalsClosed(this);
super.tearDown();
}
@Override
public Properties getProperties() {
File file;
try {
file = File.createTempFile(getName(), Options.JNL);
file.deleteOnExit();
} catch (IOException e) {
throw new RuntimeException(e);
}
final Properties properties = new Properties();
properties.setProperty(Options.BUFFER_MODE, BufferMode.DiskRW.toString());
properties.setProperty(Options.FILE, file.toString());
properties.setProperty(Journal.Options.INITIAL_EXTENT, ""
+ Bytes.megabyte * 10);
return properties;
}
static private class AbortException extends RuntimeException {
private static final long serialVersionUID = 1L;
AbortException(String msg) {
super(msg);
}
}
/**
* In this test we want to run through some data inserts, commits and aborts.
*
* The overridden Journal will fail to abort correctly by overriding
* the discardcommitters method that AbstractJournal calls after calling bufferStragey.reset().
*
* @throws InterruptedException
*/
public void test_simpleAbortFailure() throws InterruptedException {
// Define atomic to control whether abort should succeed or fail
final AtomicBoolean succeed = new AtomicBoolean(true);
final Journal jnl = new Journal(getProperties()) {
@Override
protected void discardCommitters() {
if (succeed.get()) {
super.discardCommitters();
} else {
throw new AbortException("Something wrong");
}
}
};
final RWStrategy strategy = (RWStrategy) jnl.getBufferStrategy();
final RWStore store = strategy.getStore();
final String btreeName = "TestBTreeAbort";
// 1) Create and commit some data
// 2) Create more data and Abort success
// 4) Create and commit more data (should work)
// 3) Create more data and Abort fail
// 4) Create and commit more data (should fail)
BTree btree = createBTree(jnl);
jnl.registerIndex(btreeName, btree);
btree.writeCheckpoint();
jnl.commit();
System.out.println("Start Commit Counter: " + jnl.getCommitRecord().getCommitCounter());
// 1) Add some data and commit
addSomeData(btree);
btree.writeCheckpoint();
jnl.commit();
System.out.println("After Data Commit Counter: " + jnl.getCommitRecord().getCommitCounter());
btree.close(); // force re-open
btree = jnl.getIndex(btreeName);
addSomeData(btree);
btree.writeCheckpoint();
jnl.commit();
// Show Allocators
final StringBuilder sb1 = new StringBuilder();
store.showAllocators(sb1);
if(log.isInfoEnabled()) log.info(sb1.toString());
// 2) Add more data and abort
if(log.isInfoEnabled()) log.info("Pre Abort Commit Counter: " + jnl.getCommitRecord().getCommitCounter());
btree.close(); // force re-open
addSomeData(btree);
btree.writeCheckpoint();
jnl.abort();
if(log.isInfoEnabled()) log.info("Post Abort Commit Counter: " + jnl.getCommitRecord().getCommitCounter());
btree.close(); // force re-open after abort
btree = jnl.getIndex(btreeName);
// Show Allocators again (should be the same visually)
final StringBuilder sb2 = new StringBuilder();
store.showAllocators(sb2);
if(log.isInfoEnabled()) log.info("After Abort\n" + sb2.toString());
// 3) More data and commit
addSomeData(btree);
btree.writeCheckpoint();
jnl.commit();
// Show Allocators
final StringBuilder sb3 = new StringBuilder();
store.showAllocators(sb3);
if(log.isInfoEnabled()) log.info("After More Data\n" + sb3.toString());
// 4) More data and bad abort
addSomeData(btree);
btree.writeCheckpoint();
succeed.set(false);
try {
jnl.abort();
fail();
} catch (Exception e) {
// Check the Abort was Aborted
assertTrue(InnerCause.isInnerCause(e, AbortException.class));
// good, let's see what state it is in now
}
btree.close();
// 5) More data and bad commit (after bad abort)
final BTree btree2 = jnl.getIndex(btreeName); // Note: BTree was marked as invalid. Must be reloaded.
assertTrue(btree != btree2); // Must be different references.
btree = btree2;
try {
addSomeData(btree);
btree.writeCheckpoint();
jnl.commit();
fail();
} catch (Throwable e) {
if(log.isInfoEnabled()) log.info("Expected exception", e);
succeed.set(true);
jnl.abort(); // successful abort!
}
btree = jnl.getIndex(btreeName);
// 6) More data and good commit (after good abort)
addSomeData(btree);
btree.writeCheckpoint();
jnl.commit();
}
private void addSomeData(final BTree btree) {
final Random r = new Random();
for (int n = 0; n < 2000; n++) {
final byte[] key = new byte[64];
final byte[] value = new byte[256];
r.nextBytes(key);
r.nextBytes(value);
btree.insert(key, value);
}
}
private BTree createBTree(final Journal store) {
final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID());
return BTree.create(store, metadata);
}
}