/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.test.mt; import com.foundationdb.server.error.ErrorCode; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; import java.util.Random; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class IdentityRestartWithMT extends PostgresMTBase { private static final Logger LOG = LoggerFactory.getLogger(IdentityRestartWithMT.class); private static final int TOTAL_MODE_EXECS = 1000; private static final String SQL_BEGIN = "BEGIN"; private static final String SQL_COMMIT = "COMMIT"; private static final String SQL_ROLLBACK = "ROLLBACK"; private static final String SQL_INSERT = "INSERT INTO t(id) VALUES (DEFAULT) RETURNING id"; private static final String SQL_DELETE = "DELETE FROM t"; private static final String SQL_SELECT = "SELECT MAX(id) FROM t"; private static final String SQL_ALTER_RESTART = "ALTER TABLE t ALTER COLUMN id RESTART WITH %d"; // Note: Arranged so RESTART won't happen unless the table is empty (i.e. INSERT takes 3 steps // and can't complete between DELETE and ALTER). // Avoids needing ti figure out current max without loss of generality. private static final String[][] MODES = { { SQL_BEGIN, SQL_INSERT, SQL_COMMIT, }, { SQL_BEGIN, SQL_INSERT, SQL_ROLLBACK, }, { SQL_BEGIN, SQL_SELECT, SQL_DELETE, SQL_COMMIT, SQL_ALTER_RESTART, }, }; private static class ConnState { public final String name; public final Connection conn; public Statement s; public String[] mode; public int step; private ConnState(String name, Connection conn) throws SQLException { this.name = name; this.conn = conn; this.conn.setAutoCommit(true); } public void step(Random r) throws SQLException { String sql = mode[step]; if(sql.contains("%d")) { sql = String.format(sql, 1 + r.nextInt(100)); } exec(sql); if(sql.equals(SQL_INSERT)) { ResultSet rs = s.getResultSet(); rs.next(); LOG.debug("{} inserted: {}", name, rs.getInt(1)); } if(++step == mode.length) { changeMode(r); } } public void exec(String sql) throws SQLException { for(;;) { if(s == null) { s = conn.createStatement(); } try { LOG.debug("{} exec: {}", name, sql); s.execute(sql); break; } catch(SQLException e) { if(!ErrorCode.STALE_STATEMENT.getFormattedValue().equals(e.getSQLState())) { throw e; } LOG.debug("{} retrying", name); s.close(); s = null; } } } public void changeMode(Random r) { this.mode = MODES[r.nextInt(MODES.length - 2)]; this.step = 0; } } @Rule public final TestWatcher watcher = new TestWatcher() { protected void failed(Throwable e, Description description) { LOG.error("Failure with seed: {}" + seed); } }; private final int seed = Math.abs((int)System.nanoTime()); private final Random random = new Random(seed); @Test public void oneConn() throws Exception { run(1); } @Test public void twoConn() throws Exception { run(2); } @Test public void tenConn() throws Exception { run(10); } private void run(int connCount) throws Exception { createTable(SCHEMA_NAME, "t", "id SERIAL NOT NULL PRIMARY KEY"); ConnState[] states = new ConnState[connCount]; for(int i = 0; i < connCount; ++i) { states[i] = new ConnState(Integer.toString(i), createConnection()); states[i].changeMode(random); } int loops = TOTAL_MODE_EXECS / connCount; for(int i = 0; i < loops; ++i) { LOG.debug("Loop {}", i); for(ConnState ch : states) { try { ch.step(random); } catch(SQLException e) { if(!ErrorCode.valueOfCode(e.getSQLState()).isRollbackClass()) { fail("Unexpected failure during " + Arrays.toString(ch.mode) + "[" + ch.step + "]: " + e.getMessage()); } else { LOG.debug(e.getMessage()); ch.s.execute(SQL_ROLLBACK); ch.changeMode(random); } } } } } }