package org.voltdb.regressionsuites;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import junit.framework.Test;
import org.voltdb.BackendTarget;
import org.voltdb.VoltSystemProcedure;
import org.voltdb.client.Client;
import org.voltdb.client.ClientResponse;
import org.voltdb.client.ProcCallException;
import org.voltdb.client.ProcedureCallback;
import org.voltdb.regressionsuites.specexecprocs.RemoteIdle;
import org.voltdb.regressionsuites.specexecprocs.UpdateAll;
import org.voltdb.regressionsuites.specexecprocs.UpdateOne;
import org.voltdb.sysprocs.AdHoc;
import org.voltdb.types.SpeculationConflictCheckerType;
import edu.brown.HStoreSiteTestUtil.LatchableProcedureCallback;
import edu.brown.benchmark.tm1.TM1Client;
import edu.brown.benchmark.tm1.TM1Client.Transaction;
import edu.brown.benchmark.tm1.TM1Constants;
import edu.brown.benchmark.tm1.TM1ProjectBuilder;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.utils.CollectionUtil;
import edu.brown.utils.ThreadUtil;
/**
* Simple test suite for the TM1 benchmark
* @author pavlo
*/
public class TestSpecExecSuite extends RegressionSuite {
private static final String PREFIX = "specexec";
private static final double SCALEFACTOR = 0.0001;
/**
* Constructor needed for JUnit. Should just pass on parameters to superclass.
* @param name The name of the method to test. This is just passed to the superclass.
*/
public TestSpecExecSuite(String name) {
super(name);
}
/**
* testConflictingTxns
*/
public void testConflictingTxns() throws Exception {
Client client = this.getClient();
RegressionSuiteUtil.initializeTM1Database(this.getCatalogContext(), client);
// Submit a distributed txn and make sure that our conflicting
// txn is not speculatively executed
final int sleepTime = 5000; // ms
final LatchableProcedureCallback dtxnCallback = new LatchableProcedureCallback(1);
// We're going to first execute a dtxn that updates all SUBSCRIBER records
String dtxnProcName = UpdateAll.class.getSimpleName();
Object dtxnParams[] = { 0, sleepTime };
client.callProcedure(dtxnCallback, dtxnProcName, dtxnParams);
// Then fire off a proc that updates SUBSCRIBER as well. This should never
// be allowed to execute speculatively
String spProcName = UpdateOne.class.getSimpleName();
Object spParams[] = new Object[]{ 1 };
LatchableProcedureCallback spCallback = new LatchableProcedureCallback(1);
ThreadUtil.sleep(100);
client.callProcedure(spCallback, spProcName, spParams);
// Wait until we have both latches
dtxnCallback.latch.await(sleepTime*2, TimeUnit.MILLISECONDS);
spCallback.latch.await(sleepTime*2, TimeUnit.MILLISECONDS);
// Then verify the DTXN results
ClientResponse dtxnResponse = CollectionUtil.first(dtxnCallback.responses);
assertNotNull(dtxnResponse);
assertEquals(Status.OK, dtxnResponse.getStatus());
assertTrue(dtxnResponse.hasDebug());
assertFalse(dtxnResponse.isSinglePartition());
assertFalse(dtxnResponse.isSpeculative());
// And the SP results. Where is your god now?
ClientResponse spResponse = CollectionUtil.first(spCallback.responses);
assertNotNull(spResponse);
assertEquals(Status.OK, spResponse.getStatus());
assertTrue(spResponse.hasDebug());
assertTrue(spResponse.isSinglePartition());
// There is currently a race condition for whether the txn will get
// speculatively executed or not, so for now we'll just disable this
// one check.
// sassertTrue(spResponse.isSpeculative());
// SANITY CHECK
// We should have exaclty two different MSC_LOCATION values
String procName = VoltSystemProcedure.procCallName(AdHoc.class);
String query = "SELECT COUNT(DISTINCT MSC_LOCATION) FROM " + TM1Constants.TABLENAME_SUBSCRIBER;
ClientResponse cresponse = client.callProcedure(procName, query);
assertEquals(Status.OK, cresponse.getStatus());
assertEquals(2, cresponse.getResults()[0].asScalarLong());
System.err.println(cresponse);
}
/**
* testRemoteIdle
*/
public void testRemoteIdle() throws Exception {
Client client = this.getClient();
RegressionSuiteUtil.initializeTM1Database(this.getCatalogContext(), client);
final int sleepTime = 10000; // ms
final ClientResponse dtxnResponse[] = new ClientResponse[1];
final AtomicBoolean latch = new AtomicBoolean(true);
final ProcedureCallback callback = new ProcedureCallback() {
@Override
public void clientCallback(ClientResponse clientResponse) {
// System.err.println("DISTRUBTED RESULT " + clientResponse);
dtxnResponse[0] = clientResponse;
latch.set(false);
}
};
// We're going to first execute a long running and slow distributed transaction
// on the first partition. It will sleep for 10 seconds
String procName = RemoteIdle.class.getSimpleName();
Object params[] = { 0, sleepTime };
client.callProcedure(callback, procName, params);
long start = System.currentTimeMillis();
// While we're waiting for that to come back, we're going to fire off
// a bunch of single-partition txns that should all be executed
// speculatively at the other partition
TM1Client.Transaction txn = Transaction.GET_SUBSCRIBER_DATA;
params = new Object[]{ 1 };
ClientResponse cresponse = null;
int specexec_ctr = 0;
while (latch.get()) {
// Just sleep for a little bit so that we don't blast the cluster
ThreadUtil.sleep(500);
// System.err.println("Requesting " + txn + " for speculative execution");
try {
cresponse = client.callProcedure(txn.callName, params);
assertEquals(Status.OK, cresponse.getStatus());
} catch (ProcCallException ex) {
cresponse = ex.getClientResponse();
assertEquals(cresponse.toString(), Status.ABORT_USER, cresponse.getStatus());
}
// System.err.println(cresponse.toString());
// System.err.println();
// We'll only check the txns half way through the dtxns expected
// sleep time
long elapsed = System.currentTimeMillis() - start;
assert(elapsed <= sleepTime*1.25);
if (elapsed < sleepTime/2) {
assertEquals(cresponse.toString(), latch.get(), cresponse.isSpeculative());
System.err.println(cresponse.getDebug());
}
if (cresponse.isSpeculative()) specexec_ctr++;
} // WHILE
assert(specexec_ctr > 0);
cresponse = dtxnResponse[0];
assertNotNull(cresponse);
assertTrue(cresponse.hasDebug());
assertFalse(cresponse.isSinglePartition());
assertFalse(cresponse.isSpeculative());
}
public static Test suite() {
VoltServerConfig config = null;
// the suite made here will all be using the tests from this class
MultiConfigSuiteBuilder builder = new MultiConfigSuiteBuilder(TestSpecExecSuite.class);
builder.setGlobalConfParameter("client.scalefactor", SCALEFACTOR);
builder.setGlobalConfParameter("site.txn_client_debug", true);
builder.setGlobalConfParameter("site.specexec_enable", true);
builder.setGlobalConfParameter("site.specexec_ignore_all_local", false);
builder.setGlobalConfParameter("site.specexec_scheduler_checker", SpeculationConflictCheckerType.TABLE);
// build up a project builder for the TPC-C app
TM1ProjectBuilder project = new TM1ProjectBuilder();
project.addAllDefaults();
project.addProcedure(RemoteIdle.class);
project.addProcedure(UpdateAll.class);
project.addProcedure(UpdateOne.class);
boolean success;
/////////////////////////////////////////////////////////////
// CONFIG #1: 1 Local Site with 2 Partitions running on JNI backend
/////////////////////////////////////////////////////////////
config = new LocalSingleProcessServer(PREFIX + "-2part.jar", 2, BackendTarget.NATIVE_EE_JNI);
success = config.compile(project);
assert(success);
builder.addServerConfig(config);
////////////////////////////////////////////////////////////
// CONFIG #2: cluster of 2 nodes running 2 site each, one replica
////////////////////////////////////////////////////////////
config = new LocalCluster(PREFIX + "-cluster.jar", 2, 2, 1, BackendTarget.NATIVE_EE_JNI);
success = config.compile(project);
assert(success);
builder.addServerConfig(config);
return builder;
}
}