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 h2-database+unsubscr...@googlegroups.com.
To post to this group, send email to h2-database@googlegroups.com.
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