/* $HeadURL$
* $Id$
*
* Copyright (c) 2009-2010 DuraSpace
* http://duraspace.org
*
* In collaboration with Topaz Inc.
* http://www.topazproject.org
*
* 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 org.akubraproject.txn.derby;
import java.io.File;
import java.net.URI;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.Random;
import org.apache.commons.io.FileUtils;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.fail;
import org.akubraproject.Blob;
import org.akubraproject.BlobStoreConnection;
import org.akubraproject.mem.MemBlobStore;
import org.akubraproject.tck.TCKTestSuite;
import org.akubraproject.txn.ConcurrentBlobUpdateException;
/**
* Unit tests for {@link TransactionalStore}.
*
* @author Ronald Tschalär
*/
public class TestTransactionalStore extends TCKTestSuite {
private final File dbDir;
private final boolean singleWriter;
public TestTransactionalStore() throws Exception {
super(getStore(), getStoreId(), true, false);
dbDir = getDbDir();
singleWriter = ((TransactionalStore) store).singleWriter();
/*
java.util.logging.LogManager.getLogManager().readConfiguration(
new java.io.FileInputStream("/tmp/jdklog.properties"));
*/
}
private static URI getStoreId() {
return URI.create("urn:example:txnstore");
}
private static File getDbDir() throws Exception {
File base = new File(System.getProperty("basedir"), "target");
return new File(base, "txn-db");
}
private static TransactionalStore getStore() throws Exception {
File dbDir = getDbDir();
FileUtils.deleteDirectory(dbDir);
dbDir.getParentFile().mkdirs();
File base = new File(System.getProperty("basedir"), "target");
System.setProperty("derby.stream.error.file", new File(base, "derby.log").toString());
return new TransactionalStore(getStoreId(), new MemBlobStore(), dbDir.getAbsolutePath());
}
@Override
protected URI getInvalidId() {
// one too long
StringBuilder uri = new StringBuilder("urn:blobIdValidation");
for (int idx = 0; idx < 98; idx++)
uri.append("oooooooooo");
uri.append("x");
return URI.create(uri.toString() + "x");
}
/** all URI's are distinct */
@Override
protected URI[] getAliases(URI uri) {
return new URI[] { uri };
}
@Override
public void testListBlobs() throws Exception {
super.testListBlobs();
// test when there are old versions too
URI id1 = createId("blobBasicList1");
URI id2 = createId("blobBasicList2");
createBlob(id1, "hello", true);
createBlob(id2, "bye", true);
final boolean[] cv = new boolean[] { false };
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
waitFor(cv, true, 0);
}
}, false);
}
});
listBlobs(getPrefixFor("blobBasicList"), new URI[] { id1, id2 });
listBlobs(getPrefixFor("blobBasicLisT"), new URI[] { });
listBlobs(getPrefixFor("blobBasicList2"), new URI[] { id2 });
deleteBlob(id1, "hello", true);
listBlobs(getPrefixFor("blobBasicList"), new URI[] { id2 });
listBlobs(getPrefixFor("blobBasicLisT"), new URI[] { });
listBlobs(getPrefixFor("blobBasicList2"), new URI[] { id2 });
deleteBlob(id2, "bye", true);
listBlobs(getPrefixFor("blobBasicList"), new URI[] { });
listBlobs(getPrefixFor("blobBasicLisT"), new URI[] { });
listBlobs(getPrefixFor("blobBasicList2"), new URI[] { });
createBlob(id1, "hello2", true);
listBlobs(getPrefixFor("blobBasicList"), new URI[] { id1 });
listBlobs(getPrefixFor("blobBasicLisT"), new URI[] { });
listBlobs(getPrefixFor("blobBasicList1"), new URI[] { id1 });
deleteBlob(id1, "hello2", true);
listBlobs(getPrefixFor("blobBasicList"), new URI[] { });
listBlobs(getPrefixFor("blobBasicLisT"), new URI[] { });
listBlobs(getPrefixFor("blobBasicList1"), new URI[] { });
notify(cv, true);
}
/**
* Test deletions are done and cleaned up properly under various combinations of
* creating/moving/deleting blobs.
*/
@Test(groups={ "blobs" }, dependsOnGroups={ "init" })
public void testDeleteCleanup() throws Exception {
final URI id = URI.create("urn:blobBlobDelete1");
final URI id2 = URI.create("urn:blobBlobDelete2");
final String body = "value";
// create-delete in one txn
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, null);
createBlob(con, b, null);
deleteBlob(con, b);
}
}, true);
// create-delete-create in one txn
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, null);
createBlob(con, b, null);
deleteBlob(con, b);
createBlob(con, b, null);
}
}, true);
// delete-create-delete in one txn
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, "");
deleteBlob(con, b);
createBlob(con, b, null);
deleteBlob(con, b);
}
}, true);
// create-delete-create-delete in one txn
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, null);
createBlob(con, b, null);
deleteBlob(con, b);
createBlob(con, b, null);
deleteBlob(con, b);
}
}, true);
// create-update-delete-create-update-delete in one txn
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, null);
createBlob(con, b, body);
deleteBlob(con, b);
createBlob(con, b, body);
deleteBlob(con, b);
}
}, true);
// create in one, delete in another
createBlob(id, body, true);
deleteBlob(id, body, true);
// create in one, delete + create in another
createBlob(id, body, true);
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, body);
deleteBlob(con, b);
createBlob(con, b, null);
}
}, true);
// delete + create + update in one
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, "");
deleteBlob(con, b);
createBlob(con, b, body);
}
}, true);
// update + delete + create + update in one
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, body);
setBlob(con, b, "foo");
deleteBlob(con, b);
createBlob(con, b, body);
}
}, true);
// delete + create + update + delete in one
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, body);
deleteBlob(con, b);
createBlob(con, b, body);
deleteBlob(con, b);
}
}, true);
// create-move-delete in one txn
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, null);
Blob b2 = getBlob(con, id2, null);
createBlob(con, b, null);
moveBlob(con, b, id2, "");
deleteBlob(con, b2);
}
}, true);
// create in one, move-delete in another txn
createBlob(id, body, true);
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, body);
Blob b2 = getBlob(con, id2, null);
moveBlob(con, b, id2, body);
deleteBlob(con, b2);
}
}, true);
// create-move-delete-create-move in one txn
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, null);
Blob b2 = getBlob(con, id2, null);
createBlob(con, b, null);
moveBlob(con, b, id2, "");
deleteBlob(con, b2);
createBlob(con, b2, null);
moveBlob(con, b2, id, "");
setBlob(con, b, body);
}
}, true);
// move-delete-create-move-again-yada-yada...
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id, body);
Blob b2 = getBlob(con, id2, null);
moveBlob(con, b, id2, body);
deleteBlob(con, b2);
createBlob(con, b, null);
moveBlob(con, b, id2, "");
moveBlob(con, b2, id, "");
deleteBlob(con, b);
createBlob(con, b2, null);
moveBlob(con, b2, id, "");
setBlob(con, b, body);
moveBlob(con, b, id2, body);
deleteBlob(con, b2);
createBlob(con, b, body);
}
}, true);
// clean up
deleteBlob(id, body, true);
getBlob(id, null, true);
assertNoBlobs("urn:blobBlobDelete");
}
/**
* Test conflicts between two transactions (creating, updating, or deleting a blob that
* the other has touched).
*/
@Test(groups={ "blobs" }, dependsOnGroups={ "init" })
public void testConflicts() throws Exception {
if (singleWriter)
return;
final URI id1 = URI.create("urn:blobConflict1");
final URI id2 = URI.create("urn:blobConflict2");
final String body1 = "original blob";
final String body11 = "modified blob";
final String body2 = "create me";
// create blob1
createBlob(id1, body1, true);
// create a set of actions
ConAction createNoBody = new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id2, null);
createBlob(con, b, null);
}
};
ConAction createWithBody = new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id2, null);
createBlob(con, b, body2);
}
};
ConAction delete1 = new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id1, body1);
deleteBlob(con, b);
}
};
ConAction modify1 = new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b = getBlob(con, id1, body1);
setBlob(con, b, body11);
}
};
ConAction rename12 = new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
Blob b1 = getBlob(con, id1, body1);
moveBlob(con, b1, id2, body1);
}
};
// test create-create conflict
testConflict(createNoBody, createNoBody, id2, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id2, "", true);
deleteBlob(id2, "", true);
}
});
testConflict(createWithBody, createNoBody, id2, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id2, body2, true);
deleteBlob(id2, body2, true);
}
});
testConflict(createWithBody, createWithBody, id2, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id2, body2, true);
deleteBlob(id2, body2, true);
}
});
testConflict(createNoBody, createWithBody, id2, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id2, "", true);
deleteBlob(id2, "", true);
}
});
// test delete-delete conflict
testConflict(delete1, delete1, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, null, true);
createBlob(id1, body1, true);
}
});
// test delete-modify conflict
testConflict(delete1, modify1, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, null, true);
createBlob(id1, body1, true);
}
});
testConflict(modify1, delete1, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, body11, true);
setBlob(id1, body1, true);
}
});
// test modify-modify conflict
testConflict(modify1, modify1, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, body11, true);
setBlob(id1, body1, true);
}
});
// test rename-rename conflict
testConflict(rename12, rename12, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id2, body1, true);
renameBlob(id2, id1, body1, true);
}
});
// test rename-modify conflict
testConflict(rename12, modify1, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id2, body1, true);
renameBlob(id2, id1, body1, true);
}
});
testConflict(modify1, rename12, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, body11, true);
setBlob(id1, body1, true);
}
});
// test rename-create conflict
testConflict(rename12, createNoBody, id2, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id2, body1, true);
renameBlob(id2, id1, body1, true);
}
});
testConflict(createNoBody, rename12, id2, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, body1, true);
getBlob(id2, "", true);
deleteBlob(id2, "", true);
}
});
testConflict(createWithBody, rename12, id2, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, body1, true);
getBlob(id2, body2, true);
deleteBlob(id2, body2, true);
}
});
// test rename-delete conflict
testConflict(rename12, delete1, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id2, body1, true);
renameBlob(id2, id1, body1, true);
}
});
testConflict(delete1, rename12, id1, new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, null, true);
createBlob(id1, body1, true);
}
});
// clean up
deleteBlob(id1, body1, true);
getBlob(id1, null, true);
assertNoBlobs("urn:blobConflict");
}
private void testConflict(final ConAction first, final ConAction second, final URI id,
final ERunnable reset) throws Exception {
final boolean[] cv = new boolean[] { false };
final boolean[] failed = new boolean[] { false };
Thread[] threads = new Thread[2];
// Test two threads, both operations occurring while the other hasn't committed yet
threads[0] = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
notifyAndWait(cv, true);
first.run(con);
notifyAndWait(cv, true);
}
}, true);
TestTransactionalStore.notify(cv, true);
}
}, failed);
threads[1] = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
waitFor(cv, true, 0);
notifyAndWait(cv, false);
try {
second.run(con);
fail("Did not get expected ConcurrentBlobUpdateException");
} catch (ConcurrentBlobUpdateException cbue) {
assertEquals(cbue.getBlobId(), id);
}
notifyAndWait(cv, false);
}
}, false);
}
}, failed);
for (int t = 0; t < threads.length; t++)
threads[t].join();
assertFalse(failed[0]);
reset.erun();
/* Test two threads, both starting, then the first doing its operation and comitting, then the
* second doing its operation.
*/
cv[0] = false;
threads[0] = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
notifyAndWait(cv, true);
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
notifyAndWait(cv, true);
first.run(con);
}
}, true);
TestTransactionalStore.notify(cv, true);
}
}, failed);
threads[1] = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
waitFor(cv, true, 0);
notifyAndWait(cv, false);
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
notifyAndWait(cv, false);
try {
second.run(con);
fail("Did not get expected ConcurrentBlobUpdateException");
} catch (ConcurrentBlobUpdateException cbue) {
assertEquals(cbue.getBlobId(), id);
}
}
}, false);
}
}, failed);
for (int t = 0; t < threads.length; t++)
threads[t].join();
assertFalse(failed[0]);
reset.erun();
}
/**
* Create, get, rename, update, delete in other transactions should not affect current
* transaction.
*/
@Test(groups={ "blobs" }, dependsOnGroups={ "init" })
public void testBasicTransactionIsolation() throws Exception {
if (singleWriter)
return;
final URI id1 = URI.create("urn:blobBasicTxnIsol1");
final URI id2 = URI.create("urn:blobBasicTxnIsol2");
final URI id3 = URI.create("urn:blobBasicTxnIsol3");
final URI id4 = URI.create("urn:blobBasicTxnIsol4");
final String body1 = "long lived blob";
final String body2 = "long lived, modified blob";
final String body3 = "create me";
final String body4 = "delete me";
final String body22 = "body1, v2";
final String body42 = "delete me, v2";
final String body43 = "delete me, v3";
// create blob1
createBlob(id1, body1, true);
// first start txn1, then run a bunch of other transactions while txn1 is active
doInTxn(new ConAction() {
public void run(final BlobStoreConnection con) throws Exception {
// check our snapshot
getBlob(con, id1, body1);
getBlob(con, id2, null);
getBlob(con, id3, null);
getBlob(con, id4, null);
// create another blob and modify
Blob b = getBlob(con, id2, null);
createBlob(con, b, body1);
setBlob(con, b, body2);
// create a new blob and verify we don't see it but others see it
boolean[] failed = new boolean[] { false };
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
createBlob(id3, body3, true);
}
}, failed).join();
assertFalse(failed[0]);
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, body1, true);
getBlob(id2, null, true);
getBlob(id3, body3, true);
getBlob(id4, null, true);
}
}, failed).join();
assertFalse(failed[0]);
getBlob(con, id1, body1);
getBlob(con, id2, body2);
getBlob(con, id3, null);
getBlob(con, id4, null);
// delete the new blob
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
deleteBlob(id3, body3, true);
}
}, failed).join();
assertFalse(failed[0]);
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, body1, true);
getBlob(id2, null, true);
getBlob(id3, null, true);
getBlob(id4, null, true);
}
}, failed).join();
assertFalse(failed[0]);
getBlob(con, id1, body1);
getBlob(con, id2, body2);
getBlob(con, id3, null);
getBlob(con, id4, null);
// delete the first blob
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
deleteBlob(id1, body1, true);
}
}, failed).join();
assertFalse(failed[0]);
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, null, true);
getBlob(id2, null, true);
getBlob(id3, null, true);
getBlob(id4, null, true);
}
}, failed).join();
assertFalse(failed[0]);
getBlob(con, id1, body1);
getBlob(con, id2, body2);
getBlob(con, id3, null);
getBlob(con, id4, null);
// re-create the first blob, but with different content
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
createBlob(id1, body22, true);
}
}, failed).join();
assertFalse(failed[0]);
doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
getBlob(id1, body22, true);
getBlob(id2, null, true);
getBlob(id3, null, true);
getBlob(id4, null, true);
}
}, failed).join();
assertFalse(failed[0]);
getBlob(con, id1, body1);
getBlob(con, id2, body2);
getBlob(con, id3, null);
getBlob(con, id4, null);
// step through, making sure we don't see anything from active transactions
final boolean[] cv = new boolean[1];
Thread t = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
doInTxn(new ConAction() {
public void run(BlobStoreConnection c2) throws Exception {
Blob b = getBlob(c2, id4, null);
createBlob(c2, b, body4);
notifyAndWait(cv, true);
deleteBlob(c2, b);
notifyAndWait(cv, true);
createBlob(c2, b, body42);
notifyAndWait(cv, true);
setBlob(c2, b, body43);
notifyAndWait(cv, true);
}
}, true);
}
}, failed);
waitFor(cv, true, 0);
assertFalse(con.getBlob(id4, null).exists());
notifyAndWait(cv, false);
assertFalse(con.getBlob(id4, null).exists());
notifyAndWait(cv, false);
assertFalse(con.getBlob(id4, null).exists());
notifyAndWait(cv, false);
assertFalse(con.getBlob(id4, null).exists());
TestTransactionalStore.notify(cv, false);
t.join();
assertFalse(failed[0]);
assertFalse(con.getBlob(id4, null).exists());
}
}, true);
deleteBlob(id1, body22, true);
deleteBlob(id2, body2, true);
deleteBlob(id3, null, true);
deleteBlob(id4, body43, true);
assertNoBlobs("urn:blobBasicTxnIsol");
}
/**
* Test single stepping two transactions.
*/
@Test(groups={ "blobs" }, dependsOnGroups={ "init" })
public void testTransactionIsolation2() throws Exception {
if (singleWriter)
return;
final URI id1 = URI.create("urn:blobTxnIsol2_1");
final URI id2 = URI.create("urn:blobTxnIsol2_2");
final URI id3 = URI.create("urn:blobTxnIsol2_3");
final URI id4 = URI.create("urn:blobTxnIsol2_4");
final URI id5 = URI.create("urn:blobTxnIsol2_5");
final URI id6 = URI.create("urn:blobTxnIsol2_6");
final String body1 = "blob1";
final String body2 = "blob2";
final String body3 = "blob3";
final String body4 = "blob4";
final boolean[] failed = new boolean[] { false };
final boolean[] cv = new boolean[] { false };
final Thread[] threads = new Thread[2];
// 2 threads, which will single-step, each doing a step and then waiting for the other
for (int t = 0; t < threads.length; t++) {
final URI[] ids = new URI[] { t == 0 ? id1 : id3, t == 0 ? id2 : id4 };
final URI[] oids = new URI[] { t != 0 ? id1 : id3, t != 0 ? id2 : id4 };
final URI tid = t == 0 ? id5 : id6;
final String[] bodies = new String[] { t == 0 ? body1 : body3, t == 0 ? body2 : body4 };
final String[] obodies = new String[] { t != 0 ? body1 : body3, t != 0 ? body2 : body4 };
final boolean cvVal = (t == 0);
threads[t] = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
// create two blobs
doInTxn(new ConAction() {
public void run(final BlobStoreConnection con) throws Exception {
getBlob(con, id1, false);
getBlob(con, id2, false);
getBlob(con, id3, false);
getBlob(con, id4, false);
waitFor(cv, cvVal, 0);
notifyAndWait(cv, !cvVal);
for (int idx = 0; idx < 2; idx++ ) {
Blob b = getBlob(con, ids[idx], null);
createBlob(con, b, null);
for (int idx2 = 0; idx2 < 2; idx2++ )
getBlob(con, oids[idx2], false);
notifyAndWait(cv, !cvVal);
setBlob(con, b, bodies[idx]);
notifyAndWait(cv, !cvVal);
}
}
}, true);
notifyAndWait(cv, !cvVal);
// exchange the two blobs
doInTxn(new ConAction() {
public void run(final BlobStoreConnection con) throws Exception {
getBlob(con, id1, body1);
getBlob(con, id2, body2);
getBlob(con, id3, body3);
getBlob(con, id4, body4);
notifyAndWait(cv, !cvVal);
URI[][] seq = new URI[][] {
{ ids[0], tid },
{ ids[1], ids[0] },
{ tid, ids[1] },
};
for (int idx = 0; idx < seq.length; idx++ ) {
Blob bs = getBlob(con, seq[idx][0], true);
moveBlob(con, bs, seq[idx][1], null);
notifyAndWait(cv, !cvVal);
}
}
}, true);
notifyAndWait(cv, !cvVal);
// delete the two blobs
doInTxn(new ConAction() {
public void run(final BlobStoreConnection con) throws Exception {
getBlob(con, id1, body2);
getBlob(con, id2, body1);
getBlob(con, id3, body4);
getBlob(con, id4, body3);
notifyAndWait(cv, !cvVal);
for (int idx = 0; idx < 2; idx++ ) {
Blob b = getBlob(con, ids[idx], bodies[1 - idx]);
deleteBlob(con, b);
for (int idx2 = 0; idx2 < 2; idx2++ )
getBlob(con, oids[idx2], obodies[1 - idx2]);
notifyAndWait(cv, !cvVal);
}
}
}, true);
notifyAndWait(cv, !cvVal);
TestTransactionalStore.notify(cv, !cvVal);
}
}, failed);
}
for (int t = 0; t < threads.length; t++)
threads[t].join();
assertFalse(failed[0]);
assertNoBlobs("urn:blobTxnIsol2_");
}
/**
* Stress test the stuff a bit.
*/
@Test(groups={ "blobs" }, dependsOnGroups={ "init" })
public void stressTest() throws Exception {
// get our config
final int numFillers = Integer.getInteger("akubra.txn.test.numFillers", 0);
final int numReaders = Integer.getInteger("akubra.txn.test.numReaders", 10);
final int numWriters = Integer.getInteger("akubra.txn.test.numWriters", 10);
final int numObjects = Integer.getInteger("akubra.txn.test.numObjects", 10);
final int numRounds = Integer.getInteger("akubra.txn.test.numRounds", 10);
long t0 = System.currentTimeMillis();
// "fill" the db a bit
for (int b = 0; b < numFillers / 1000; b++) {
final int start = b * 1000;
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
for (int idx = start; idx < start + 1000; idx++) {
Blob b = con.getBlob(URI.create("urn:blobStressTestFiller" + idx), null);
setBody(b, "v" + idx);
}
}
}, true);
}
long t1 = System.currentTimeMillis();
// set up
Thread[] writers = new Thread[numWriters];
Thread[] readers = new Thread[numReaders];
boolean[] failed = new boolean[] { false };
final boolean[] testDone = new boolean[] { false };
final int[] lowIds = new int[numWriters];
final int[] highId = new int[] { 0 };
// start/run the writers
for (int t = 0; t < writers.length; t++) {
final int tid = t;
final int start = t * numRounds * numObjects;
writers[t] = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
for (int r = 0; r < numRounds; r++) {
final int off = start + r * numObjects;
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
for (int o = 0; o < numObjects; o++) {
int idx = off + o;
URI id = URI.create("urn:blobStressTest" + idx);
String val = "v" + idx;
Blob b = getBlob(con, id, null);
createBlob(con, b, val);
}
}
}, true);
synchronized (testDone) {
highId[0] = Math.max(highId[0], off + numObjects);
}
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
for (int o = 0; o < numObjects; o++) {
int idx = off + o;
URI id = URI.create("urn:blobStressTest" + idx);
String val = "v" + idx;
Blob b = getBlob(con, id, val);
deleteBlob(con, b);
}
}
}, true);
synchronized (testDone) {
lowIds[tid] = off + numObjects;
}
}
}
}, failed);
}
// start/run the readers
for (int t = 0; t < readers.length; t++) {
readers[t] = doInThread(new ERunnable() {
@Override
public void erun() throws Exception {
final Random rng = new Random();
final int[] found = new int[] { 0 };
while (true) {
final int low, high;
synchronized (testDone) {
if (testDone[0])
break;
high = highId[0];
int tmp = Integer.MAX_VALUE;
for (int id : lowIds)
tmp = Math.min(tmp, id);
low = tmp;
}
if (low == high) {
Thread.yield();
continue;
}
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
for (int o = 0; o < numObjects; o++) {
int idx = rng.nextInt(high - low) + low;
URI id = URI.create("urn:blobStressTest" + idx);
String val = "v" + idx;
Blob b = con.getBlob(id, null);
if (b.exists()) {
assertEquals(getBody(b), val);
found[0]++;
}
}
}
}, true);
}
if (found[0] == 0)
System.out.println("Warning: this reader found no blobs");
}
}, failed);
}
// wait for things to end
for (int t = 0; t < writers.length; t++)
writers[t].join();
synchronized (testDone) {
testDone[0] = true;
}
for (int t = 0; t < readers.length; t++)
readers[t].join();
long t2 = System.currentTimeMillis();
// remove the fillers again
for (int b = 0; b < numFillers / 1000; b++) {
final int start = b * 1000;
doInTxn(new ConAction() {
public void run(BlobStoreConnection con) throws Exception {
for (int idx = start; idx < start + 1000; idx++)
con.getBlob(URI.create("urn:blobStressTestFiller" + idx), null).delete();
}
}, true);
}
long t3 = System.currentTimeMillis();
System.out.println("Time to create " + numFillers + " fillers: " + ((t1 - t0) / 1000.) + " s");
System.out.println("Time to remove " + numFillers + " fillers: " + ((t3 - t2) / 1000.) + " s");
System.out.println("Time to run test (" + numWriters + "/" + numRounds + "/" + numObjects +
"): " + ((t2 - t1) / 1000.) + " s");
assertFalse(failed[0]);
}
/**
* Test that things get cleaned up. This runs after all other tests that create or otherwise
* manipulate blobs.
*/
@Override
public void testCleanup() throws Exception {
super.testCleanup();
// verify that the tables are truly empty
Connection connection = DriverManager.getConnection("jdbc:derby:" + dbDir);
ResultSet rs =
connection.createStatement().executeQuery("SELECT * FROM " + TransactionalStore.NAME_TABLE);
assertFalse(rs.next(), "unexpected entries in name-map table;");
rs = connection.createStatement().executeQuery("SELECT * FROM " + TransactionalStore.DEL_TABLE);
assertFalse(rs.next(), "unexpected entries in deleted-list table;");
}
private void doInTxn(ConAction a, boolean commit) throws Exception {
tm.begin();
BlobStoreConnection con = store.openConnection(tm.getTransaction(), null);
try {
a.run(con);
if (commit)
tm.commit();
else
tm.rollback();
} finally {
if (tm.getTransaction() != null) {
try {
tm.rollback();
} catch (Exception e) {
e.printStackTrace();
}
}
con.close();
}
}
}