/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.List;
import org.h2.constant.ErrorCode;
import org.h2.jdbc.JdbcConnection;
import org.h2.store.fs.FileSystem;
import org.h2.test.TestBase;
import org.h2.util.IOUtils;
import org.h2.util.SortedProperties;
import org.h2.util.Task;
/**
* Test the serialized (server-less) mode.
*/
public class TestFileLockSerialized extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
public void test() throws Exception {
println("testAutoIncrement");
testAutoIncrement();
println("testSequenceFlush");
testSequenceFlush();
println("testLeftLogFiles");
testLeftLogFiles();
println("testWrongDatabaseInstanceOnReconnect");
testWrongDatabaseInstanceOnReconnect();
println("testCache()");
testCache();
println("testBigDatabase(false)");
testBigDatabase(false);
println("testBigDatabase(true)");
testBigDatabase(true);
println("testCheckpointInUpdateRaceCondition");
testCheckpointInUpdateRaceCondition();
println("testConcurrentUpdates");
testConcurrentUpdates();
println("testThreeMostlyReaders true");
testThreeMostlyReaders(true);
println("testThreeMostlyReaders false");
testThreeMostlyReaders(false);
println("testTwoReaders");
testTwoReaders();
println("testTwoWriters");
testTwoWriters();
println("testPendingWrite");
testPendingWrite();
println("testKillWriter");
testKillWriter();
println("testConcurrentReadWrite");
testConcurrentReadWrite();
deleteDb("fileLockSerialized");
}
private void testSequenceFlush() throws Exception {
deleteDb("fileLockSerialized");
String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE";
ResultSet rs;
Connection conn1 = DriverManager.getConnection(url);
Statement stat1 = conn1.createStatement();
stat1.execute("create sequence seq");
rs = stat1.executeQuery("call seq.nextval");
rs.next();
assertEquals(1, rs.getInt(1));
Connection conn2 = DriverManager.getConnection(url);
Statement stat2 = conn2.createStatement();
rs = stat2.executeQuery("call seq.nextval");
rs.next();
assertEquals(2, rs.getInt(1));
conn1.close();
conn2.close();
}
private void testThreeMostlyReaders(final boolean write) throws Exception {
boolean longRun = false;
deleteDb("fileLockSerialized");
final String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE";
Connection conn = DriverManager.getConnection(url);
conn.createStatement().execute("create table test(id int) as select 1");
conn.close();
final int len = 10;
final Exception[] ex = { null };
final boolean[] stop = { false };
Thread[] threads = new Thread[len];
for (int i = 0; i < len; i++) {
Thread t = new Thread(new Runnable() {
public void run() {
try {
Connection c = DriverManager.getConnection(url);
PreparedStatement p = c.prepareStatement("select * from test where id = ?");
while (!stop[0]) {
Thread.sleep(100);
if (write) {
if (Math.random() > 0.9) {
c.createStatement().execute("update test set id = id");
}
}
p.setInt(1, 1);
p.executeQuery();
p.clearParameters();
}
c.close();
} catch (Exception e) {
ex[0] = e;
}
}
});
t.start();
threads[i] = t;
}
if (longRun) {
Thread.sleep(40000);
} else {
Thread.sleep(1000);
}
stop[0] = true;
for (int i = 0; i < len; i++) {
threads[i].join();
}
if (ex[0] != null) {
throw ex[0];
}
DriverManager.getConnection(url).close();
}
private void testTwoReaders() throws Exception {
deleteDb("fileLockSerialized");
String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE";
Connection conn1 = DriverManager.getConnection(url);
conn1.createStatement().execute("create table test(id int)");
Connection conn2 = DriverManager.getConnection(url);
Statement stat2 = conn2.createStatement();
stat2.execute("drop table test");
stat2.execute("create table test(id identity) as select 1");
conn2.close();
conn1.close();
DriverManager.getConnection(url).close();
}
private void testTwoWriters() throws Exception {
deleteDb("fileLockSerialized");
String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized";
final String writeUrl = url + ";FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE";
Connection conn = DriverManager.getConnection(writeUrl, "sa", "sa");
conn.createStatement().execute("create table test(id identity) as select x from system_range(1, 100)");
conn.close();
Task task = new Task() {
public void call() throws Exception {
while (!stop) {
Thread.sleep(10);
Connection c = DriverManager.getConnection(writeUrl, "sa", "sa");
c.createStatement().execute("select * from test");
c.close();
}
}
}.execute();
Thread.sleep(20);
for (int i = 0; i < 2; i++) {
conn = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat = conn.createStatement();
stat.execute("drop table test");
stat.execute("create table test(id identity) as select x from system_range(1, 100)");
conn.createStatement().execute("select * from test");
conn.close();
}
Thread.sleep(100);
conn = DriverManager.getConnection(writeUrl, "sa", "sa");
conn.createStatement().execute("select * from test");
conn.close();
task.get();
}
private void testPendingWrite() throws Exception {
deleteDb("fileLockSerialized");
String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized";
String writeUrl = url + ";FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE;WRITE_DELAY=0";
Connection conn = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat = conn.createStatement();
stat.execute("create table test(id int primary key)");
Thread.sleep(100);
String propFile = getBaseDir() + "/fileLockSerialized.lock.db";
SortedProperties p = SortedProperties.loadProperties(propFile);
p.setProperty("changePending", "true");
p.setProperty("modificationDataId", "1000");
OutputStream out = IOUtils.openFileOutputStream(propFile, false);
try {
p.store(out, "test");
} finally {
out.close();
}
Thread.sleep(100);
stat.execute("select * from test");
conn.close();
}
private void testKillWriter() throws Exception {
deleteDb("fileLockSerialized");
String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized";
String writeUrl = url + ";FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE;WRITE_DELAY=0";
Connection conn = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat = conn.createStatement();
stat.execute("create table test(id int primary key)");
((JdbcConnection) conn).setPowerOffCount(1);
assertThrows(ErrorCode.DATABASE_IS_CLOSED, stat).
execute("insert into test values(1)");
Connection conn2 = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat2 = conn2.createStatement();
stat2.execute("insert into test values(1)");
printResult(stat2, "select * from test");
conn2.close();
assertThrows(ErrorCode.DATABASE_IS_CLOSED, conn).close();
}
private void testConcurrentReadWrite() throws Exception {
deleteDb("fileLockSerialized");
String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized";
String writeUrl = url + ";FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE";
// ;TRACE_LEVEL_SYSTEM_OUT=3
// String readUrl = writeUrl + ";ACCESS_MODE_DATA=R";
trace(" create database");
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat = conn.createStatement();
stat.execute("create table test(id int primary key)");
Connection conn3 = DriverManager.getConnection(writeUrl, "sa", "sa");
PreparedStatement prep3 = conn3.prepareStatement("insert into test values(?)");
Connection conn2 = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat2 = conn2.createStatement();
printResult(stat2, "select * from test");
stat2.execute("create local temporary table temp(name varchar) not persistent");
printResult(stat2, "select * from temp");
trace(" insert row 1");
stat.execute("insert into test values(1)");
trace(" insert row 2");
prep3.setInt(1, 2);
prep3.execute();
printResult(stat2, "select * from test");
printResult(stat2, "select * from temp");
conn.close();
conn2.close();
conn3.close();
}
private void printResult(Statement stat, String sql) throws SQLException {
trace(" query: " + sql);
ResultSet rs = stat.executeQuery(sql);
int rowCount = 0;
while (rs.next()) {
trace(" " + rs.getString(1));
rowCount++;
}
trace(" " + rowCount + " row(s)");
}
private void testConcurrentUpdates() throws Exception {
boolean longRun = false;
if (longRun) {
for (int waitTime = 100; waitTime < 10000; waitTime += 20) {
for (int howManyThreads = 1; howManyThreads < 10; howManyThreads++) {
testConcurrentUpdates(waitTime, howManyThreads, waitTime * howManyThreads * 10);
}
}
} else {
testConcurrentUpdates(100, 4, 2000);
}
}
private void testAutoIncrement() throws Exception {
boolean longRun = false;
if (longRun) {
for (int waitTime = 100; waitTime < 10000; waitTime += 20) {
for (int howManyThreads = 1; howManyThreads < 10; howManyThreads++) {
testAutoIncrement(waitTime, howManyThreads, 2000);
}
}
} else {
testAutoIncrement(400, 2, 2000);
}
}
private void testAutoIncrement(final int waitTime, int howManyThreads, int runTime) throws Exception {
println("testAutoIncrement waitTime: " + waitTime + " howManyThreads: " + howManyThreads + " runTime: " + runTime);
deleteDb("fileLockSerialized");
final String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE;" +
"AUTO_RECONNECT=TRUE;MAX_LENGTH_INPLACE_LOB=8192;COMPRESS_LOB=DEFLATE;CACHE_SIZE=65536";
Connection conn = DriverManager.getConnection(url);
conn.createStatement().execute("create table test(id int auto_increment, id2 int)");
conn.close();
final long endTime = System.currentTimeMillis() + runTime;
final Exception[] ex = { null };
final Connection[] connList = new Connection[howManyThreads];
final boolean[] stop = { false };
final int[] nextInt = { 0 };
Thread[] threads = new Thread[howManyThreads];
for (int i = 0; i < howManyThreads; i++) {
final int finalNrOfConnection = i;
Thread t = new Thread(new Runnable() {
public void run() {
try {
Connection c = DriverManager.getConnection(url);
connList[finalNrOfConnection] = c;
while (!stop[0]) {
synchronized (nextInt) {
ResultSet rs = c.createStatement().executeQuery("select id, id2 from test");
while (rs.next()) {
if (rs.getInt(1) != rs.getInt(2)) {
throw new Exception(
Thread.currentThread().getId() +
" nextInt: " + nextInt [0] +
" rs.getInt(1): " + rs.getInt(1) +
" rs.getInt(2): " + rs.getInt(2));
}
}
nextInt[0]++;
Statement stat = c.createStatement();
stat.execute("insert into test (id2) values(" + nextInt[0] + ")");
ResultSet rsKeys = stat.getGeneratedKeys();
while (rsKeys.next()) {
assertEquals(nextInt[0], rsKeys.getInt(1));
}
rsKeys.close();
}
Thread.sleep(waitTime);
}
c.close();
} catch (Exception e) {
e.printStackTrace();
ex[0] = e;
}
}
});
t.start();
threads[i] = t;
}
while ((ex[0] == null) && (System.currentTimeMillis() < endTime)) {
Thread.sleep(10);
}
stop[0] = true;
for (int i = 0; i < howManyThreads; i++) {
threads[i].join();
}
if (ex[0] != null) {
throw ex[0];
}
DriverManager.getConnection(url).close();
deleteDb("fileLockSerialized");
}
private void testConcurrentUpdates(final int waitTime, int howManyThreads, int runTime) throws Exception {
println("testConcurrentUpdates waitTime: " + waitTime + " howManyThreads: " + howManyThreads + " runTime: " + runTime);
deleteDb("fileLockSerialized");
final String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE;" +
"AUTO_RECONNECT=TRUE;MAX_LENGTH_INPLACE_LOB=8192;COMPRESS_LOB=DEFLATE;CACHE_SIZE=65536";
Connection conn = DriverManager.getConnection(url);
conn.createStatement().execute("create table test(id int)");
conn.createStatement().execute("insert into test values(1)");
conn.close();
final long endTime = System.currentTimeMillis() + runTime;
final Exception[] ex = { null };
final Connection[] connList = new Connection[howManyThreads];
final boolean[] stop = { false };
final int[] lastInt = { 1 };
Thread[] threads = new Thread[howManyThreads];
for (int i = 0; i < howManyThreads; i++) {
final int finalNrOfConnection = i;
Thread t = new Thread(new Runnable() {
public void run() {
try {
Connection c = DriverManager.getConnection(url);
connList[finalNrOfConnection] = c;
while (!stop[0]) {
ResultSet rs = c.createStatement().executeQuery("select * from test");
rs.next();
if (rs.getInt(1) != lastInt[0]) {
throw new Exception(finalNrOfConnection + " Expected: " + lastInt[0] + " got " + rs.getInt(1));
}
Thread.sleep(waitTime);
if (Math.random() > 0.7) {
int newLastInt = (int) (Math.random() * 1000);
c.createStatement().execute("update test set id = " + newLastInt);
lastInt[0] = newLastInt;
}
}
c.close();
} catch (Exception e) {
e.printStackTrace();
ex[0] = e;
}
}
});
t.start();
threads[i] = t;
}
while ((ex[0] == null) && (System.currentTimeMillis() < endTime)) {
Thread.sleep(10);
}
stop[0] = true;
for (int i = 0; i < howManyThreads; i++) {
threads[i].join();
}
if (ex[0] != null) {
throw ex[0];
}
DriverManager.getConnection(url).close();
deleteDb("fileLockSerialized");
}
/**
* If a checkpoint occurs between beforeWriting and checkWritingAllowed
* then the result of checkWritingAllowed is READ_ONLY, which is wrong.
*
* Also, if a checkpoint started before beforeWriting, and ends between
* between beforeWriting and checkWritingAllowed, then the same error occurs.
*
* @throws Exception
*/
private void testCheckpointInUpdateRaceCondition() throws Exception {
boolean longRun = false;
deleteDb("fileLockSerialized");
String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE";
Connection conn = DriverManager.getConnection(url);
conn.createStatement().execute("create table test(id int)");
conn.createStatement().execute("insert into test values(1)");
for (int i = 0; i < (longRun ? 10000 : 5); i++) {
Thread.sleep(402);
conn.createStatement().execute("update test set id = " + i);
}
conn.close();
deleteDb("fileLockSerialized");
}
/**
* Caches must be cleared. Session.reconnect only closes the DiskFile (which is
* associated with the cache) if there is one session
*/
private void testCache() throws Exception {
deleteDb("fileLockSerialized");
String urlShared = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED";
Connection connShared1 = DriverManager.getConnection(urlShared);
Statement statement1 = connShared1.createStatement();
Connection connShared2 = DriverManager.getConnection(urlShared);
Statement statement2 = connShared2.createStatement();
statement1.execute("create table test1(id int)");
statement1.execute("insert into test1 values(1)");
ResultSet rs = statement1.executeQuery("select id from test1");
rs.close();
rs = statement2.executeQuery("select id from test1");
rs.close();
statement1.execute("update test1 set id=2");
Thread.sleep(500);
rs = statement2.executeQuery("select id from test1");
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
rs.close();
connShared1.close();
connShared2.close();
deleteDb("fileLockSerialized");
}
private void testWrongDatabaseInstanceOnReconnect() throws Exception {
deleteDb("fileLockSerialized");
String urlShared = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED";
String urlForNew = urlShared + ";OPEN_NEW=TRUE";
Connection connShared1 = DriverManager.getConnection(urlShared);
Statement statement1 = connShared1.createStatement();
Connection connShared2 = DriverManager.getConnection(urlShared);
Connection connNew = DriverManager.getConnection(urlForNew);
statement1.execute("create table test1(id int)");
connShared1.close();
connShared2.close();
connNew.close();
deleteDb("fileLockSerialized");
}
private void testBigDatabase(boolean withCache) throws Exception {
boolean longRun = false;
final int howMuchRows = longRun ? 2000000 : 500000;
deleteDb("fileLockSerialized");
int cacheSizeKb = withCache ? 5000 : 0;
final String url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized" +
";FILE_LOCK=SERIALIZED" +
";OPEN_NEW=TRUE" +
";CACHE_SIZE=" + cacheSizeKb;
final boolean[] importFinished = { false };
final Task importUpdate = new Task() {
public void call() throws Exception {
Connection conn = DriverManager.getConnection(url);
Statement stat = conn.createStatement();
stat.execute("create table test(id int, id2 int)");
for (int i = 0; i < howMuchRows; i++) {
stat.execute("insert into test values(" + i + ", " + i + ")");
}
importFinished[0] = true;
Thread.sleep(5000);
stat.execute("update test set id2=999 where id=500");
conn.close();
importFinished[0] = true;
}
};
importUpdate.execute();
Task select = new Task() {
public void call() throws Exception {
Connection conn = DriverManager.getConnection(url);
Statement stat = conn.createStatement();
while (!importFinished[0]) {
Thread.sleep(100);
}
Thread.sleep(1000);
ResultSet rs = stat.executeQuery("select id2 from test where id=500");
assertTrue(rs.next());
assertEquals(500, rs.getInt(1));
rs.close();
importUpdate.get();
Thread.sleep(1000);
rs = stat.executeQuery("select id2 from test where id=500");
assertTrue(rs.next());
assertEquals(999, rs.getInt(1));
rs.close();
conn.close();
}
};
select.execute();
importUpdate.get();
select.get();
deleteDb("fileLockSerialized");
}
private void testLeftLogFiles() throws Exception {
deleteDb("fileLockSerialized");
// without serialized
String url;
url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized";
Connection conn = DriverManager.getConnection(url);
Statement stat = conn.createStatement();
stat.execute("create table test(id int)");
stat.execute("insert into test values(0)");
conn.close();
FileSystem fs = FileSystem.getInstance(getBaseDir());
List<String> filesWithoutSerialized = Arrays.asList(fs.listFiles(getBaseDir()));
deleteDb("fileLockSerialized");
// with serialized
url = "jdbc:h2:" + getBaseDir() + "/fileLockSerialized;FILE_LOCK=SERIALIZED";
conn = DriverManager.getConnection(url);
stat = conn.createStatement();
stat.execute("create table test(id int)");
Thread.sleep(500);
stat.execute("insert into test values(0)");
conn.close();
List<String> filesWithSerialized = Arrays.asList(fs.listFiles(getBaseDir()));
if (filesWithoutSerialized.size() != filesWithSerialized.size()) {
for (int i = 0; i < filesWithoutSerialized.size(); i++) {
if (!filesWithSerialized.contains(filesWithoutSerialized.get(i))) {
System.out.println("File left from 'without serialized' mode: " + filesWithoutSerialized.get(i));
}
}
for (int i = 0; i < filesWithSerialized.size(); i++) {
if (!filesWithoutSerialized.contains(filesWithSerialized.get(i))) {
System.out.println("File left from 'with serialized' mode: " + filesWithSerialized.get(i));
}
}
fail("With serialized it must create the same files than without serialized");
}
deleteDb("fileLockSerialized");
}
}