/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.mvcc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import org.h2.test.TestBase;
/**
* Additional MVCC (multi version concurrency) test cases.
*/
public class TestMvcc4 extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase test = TestBase.createCaller().init();
test.config.mvcc = true;
test.config.lockTimeout = 20000;
test.config.memory = true;
test.test();
}
@Override
public void test() throws SQLException {
if (config.networked) {
return;
}
testSelectForUpdateAndUpdateConcurrency();
}
private void testSelectForUpdateAndUpdateConcurrency() throws SQLException {
Connection setup = getConnection("mvcc4");
setup.setAutoCommit(false);
{
Statement s = setup.createStatement();
s.executeUpdate("CREATE TABLE test ("
+ "entity_id VARCHAR(100) NOT NULL PRIMARY KEY, "
+ "lastUpdated TIMESTAMP NOT NULL)");
PreparedStatement ps = setup.prepareStatement(
"INSERT INTO test (entity_id, lastUpdated) VALUES (?, ?)");
for (int i = 0; i < 2; i++) {
String id = "" + i;
ps.setString(1, id);
ps.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
ps.executeUpdate();
}
setup.commit();
}
//Create a connection from thread 1
Connection c1 = getConnection("mvcc4;LOCK_TIMEOUT=10000");
c1.setAutoCommit(false);
//Fire off a concurrent update.
final Thread mainThread = Thread.currentThread();
final CountDownLatch executedUpdate = new CountDownLatch(1);
new Thread() {
@Override
public void run() {
try {
Connection c2 = getConnection("mvcc4");
c2.setAutoCommit(false);
PreparedStatement ps = c2.prepareStatement(
"SELECT * FROM test WHERE entity_id = ? FOR UPDATE");
ps.setString(1, "1");
ps.executeQuery().next();
executedUpdate.countDown();
waitForThreadToBlockOnDB(mainThread);
c2.commit();
c2.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}.start();
//Wait until the concurrent update has executed, but not yet committed
try {
executedUpdate.await();
} catch (InterruptedException e) {
// ignore
}
// Execute an update. This should initially fail, and enter the waiting
// for lock case.
PreparedStatement ps = c1.prepareStatement("UPDATE test SET lastUpdated = ?");
ps.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
ps.executeUpdate();
c1.commit();
c1.close();
Connection verify = getConnection("mvcc4");
verify.setAutoCommit(false);
ps = verify.prepareStatement("SELECT COUNT(*) FROM test");
ResultSet rs = ps.executeQuery();
assertTrue(rs.next());
assertTrue(rs.getInt(1) == 2);
verify.commit();
verify.close();
setup.close();
}
/**
* Wait for the given thread to block on synchronizing on the database
* object.
*
* @param t the thread
*/
void waitForThreadToBlockOnDB(Thread t) {
while (true) {
// sleep the first time through the loop so we give the main thread
// a chance
try {
Thread.sleep(20);
} catch (InterruptedException e1) {
// ignore
}
// TODO must not use getAllStackTraces, as the method names are
// implementation details
Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces();
StackTraceElement[] elements = threadMap.get(t);
if (elements != null
&&
elements.length > 1 &&
(config.multiThreaded ? "sleep".equals(elements[0]
.getMethodName()) : "wait".equals(elements[0]
.getMethodName())) &&
"filterConcurrentUpdate"
.equals(elements[1].getMethodName())) {
return;
}
}
}
}