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();
        }
    }
}

Reply via email to