Hi,
It turns out that the two-phase commit functionality does not work properly
with MVStore (the issue does not occur in case of PageStore). The problem
renders the two-phase commit protocol unusable. I'm attaching a standalone
testcase, please refer to the comments in the main method for more details.
Regards,
wburzyns
--
You received this message because you are subscribed to the Google Groups "H2
Database" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/h2-database.
For more options, visit https://groups.google.com/d/optout.
import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Random;
import org.h2.jdbcx.JdbcDataSource;
import org.h2.store.fs.FileUtils;
/**
A test for H2's faulty two-phase-commit.
*/
public class H2_2PC_Test
{
private static final String OBJ_TYPE = "BINARY"; // possible values are "BLOB" or "VARCHAR" or "BINARY"
private static final String DB_PATH = System.getProperty("java.io.tmpdir");
private static final String DB_NAME = "H2_2PC_Test";
private static Connection dbConn;
static void createFreshDb(boolean mvStore) throws Exception
{
// remove previous DB, if any
File testDbDir = new File(DB_PATH, DB_NAME);
FileUtils.deleteRecursive(testDbDir.getCanonicalPath(), false);
testDbDir.mkdirs();
// create a new DB
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:split:" + testDbDir.getCanonicalPath() + File.separator + DB_NAME
+ ";MV_STORE=" + mvStore
);
dbConn = dataSource.getConnection();
try (Statement st = dbConn.createStatement()) {
st.executeUpdate("CREATE SEQUENCE IF NOT EXISTS dataStampSequence CACHE 1024");
st.executeUpdate("CREATE TABLE IF NOT EXISTS dataTable("
+ "dataStamp BIGINT NOT NULL DEFAULT NEXT VALUE FOR dataStampSequence PRIMARY KEY, "
+ "data1 " + OBJ_TYPE + ", "
+ "data2 " + OBJ_TYPE + ")");
}
}
static void openExistingDb(boolean mvStore) throws Exception
{
File testDbDir = new File(DB_PATH, DB_NAME);
String testDbFileName = testDbDir.getCanonicalPath() + File.separator + DB_NAME + (mvStore ? ".mv.db" : ".h2.db");
if (!FileUtils.exists(testDbFileName)) {
throw new AssertionError();
}
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:split:" + testDbDir.getCanonicalPath() + File.separator + DB_NAME
+ ";MV_STORE=" + mvStore
+ (mvStore ? ";MAX_LOG_SIZE=1" : "")
);
dbConn = dataSource.getConnection();
// list all in-doubt txns
int inDoubtCnt = 0;
try (Statement st = dbConn.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM INFORMATION_SCHEMA.IN_DOUBT")) {
while (rs.next()) { // there is at least one in-doubt transaction
++inDoubtCnt;
String txnName = rs.getString("TRANSACTION");
String txnState = rs.getString("STATE");
if (!txnState.equals("IN_DOUBT")) {
throw new AssertionError("In-doubt reported state is unexpected");
}
System.out.println("*** txnName: " + txnName + "; txnState: " + txnState + " ***");
}
if (inDoubtCnt == 0) {
System.out.println("*** OK, no IN_DOUBT transactions found ***");
} else {
System.out.println("*** FAIL ***");
}
}
}
static void pushData(int numRows, String commitName) throws Exception
{
if (dbConn == null) {
throw new AssertionError();
}
// push data
Random rnd = new Random(0xBADDB);
try (PreparedStatement dataInsertSt = dbConn.prepareStatement("INSERT INTO dataTable VALUES("
+ "DEFAULT, "
+ "?, "
+ "?)")) {
dbConn.setAutoCommit(false);
for (int i = 0; i < numRows; ++i) {
if (dbConn.getAutoCommit() == true) {
throw new AssertionError();
}
// compressible data
byte[] data1 = new byte[rnd.nextInt((int)(100.5 * 4096)) + 1];
for (int j = 0; j < data1.length; ++j) {
data1[j] = (byte)j;
}
dataInsertSt.setBytes(1, data1);
// incompressible data
byte[] data2 = new byte[rnd.nextInt((int)(1.5 * 4096)) + 1];
rnd.nextBytes(data2);
dataInsertSt.setBytes(2, data2);
dataInsertSt.executeUpdate();
dataInsertSt.clearParameters();
}
if (commitName != null) {
try (Statement st = dbConn.createStatement()) {
st.executeUpdate("PREPARE COMMIT \"" + commitName + "\"");
}
dbConn.commit();
}
}
}
public static void main(final String[] args)
{
boolean mvStore = true; // false for PageStore, no issue then
int numRows = 100;
try {
System.out.println("*** Test DB location: " + DB_PATH + File.separator + DB_NAME + " ***");
System.out.println("*** Creating a fresh DB ***");
createFreshDb(mvStore);
System.out.println("*** Pushing data, leaving last transaction open ***");
pushData(numRows, "#1"); // open txn, push data, prepare #1, commit
pushData(numRows, "#2"); // open txn, push data, prepare #2, commit
pushData(numRows, "#3"); // open txn, push data, prepare #3, commit
pushData(numRows, null); // open txn, push data, leave the txn open
System.out.println("*** Simulating a crash ***");
try (Statement st = dbConn.createStatement()) { // simulate a crash
st.executeUpdate("SHUTDOWN IMMEDIATELY");
}
System.out.println("*** Opening DB ***");
openExistingDb(mvStore);
if (mvStore == true) {
// notice that previously resolved (committed) #3 is reported as in-doubt
// expectation is that the H2 does not report any in-doubt transaction
}
dbConn.close();
} catch (final Exception e) {
e.printStackTrace();
}
}
}