提交 d3fee387 authored 作者: Thomas Mueller's avatar Thomas Mueller

Multi-threaded kernel: concurrently running an online backup and updating the…

Multi-threaded kernel: concurrently running an online backup and updating the database resulted in a broken (transactionally incorrect) backup file in some cases.
上级 61c3b9ba
...@@ -88,15 +88,20 @@ public class BackupCommand extends Prepared { ...@@ -88,15 +88,20 @@ public class BackupCommand extends Prepared {
Database db = session.getDatabase(); Database db = session.getDatabase();
fileName = FileUtils.getName(fileName); fileName = FileUtils.getName(fileName);
out.putNextEntry(new ZipEntry(fileName)); out.putNextEntry(new ZipEntry(fileName));
int max = store.getPageCount();
int pos = 0; int pos = 0;
try {
store.setBackup(true);
while (true) { while (true) {
pos = store.copyDirect(pos, out); pos = store.copyDirect(pos, out);
if (pos < 0) { if (pos < 0) {
break; break;
} }
int max = store.getPageCount();
db.setProgress(DatabaseEventListener.STATE_BACKUP_FILE, fileName, pos, max); db.setProgress(DatabaseEventListener.STATE_BACKUP_FILE, fileName, pos, max);
} }
} finally {
store.setBackup(false);
}
out.closeEntry(); out.closeEntry();
} }
......
...@@ -7,13 +7,17 @@ ...@@ -7,13 +7,17 @@
package org.h2.test.db; package org.h2.test.db;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.concurrent.atomic.AtomicLong;
import org.h2.api.DatabaseEventListener;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.tools.Backup; import org.h2.tools.Backup;
import org.h2.tools.Restore; import org.h2.tools.Restore;
import org.h2.util.Task;
/** /**
* Test for the BACKUP SQL statement. * Test for the BACKUP SQL statement.
...@@ -33,6 +37,7 @@ public class TestBackup extends TestBase { ...@@ -33,6 +37,7 @@ public class TestBackup extends TestBase {
if (config.memory) { if (config.memory) {
return; return;
} }
testConcurrentBackup();
testBackupRestoreLobStatement(); testBackupRestoreLobStatement();
testBackupRestoreLob(); testBackupRestoreLob();
testBackup(); testBackup();
...@@ -40,6 +45,95 @@ public class TestBackup extends TestBase { ...@@ -40,6 +45,95 @@ public class TestBackup extends TestBase {
FileUtils.delete(getBaseDir() + "/backup.zip"); FileUtils.delete(getBaseDir() + "/backup.zip");
} }
private void testConcurrentBackup() throws SQLException {
if (config.networked || !config.big) {
return;
}
deleteDb("backup");
String url = getURL("backup;multi_threaded=true", true);
Connection conn = getConnection(url);
final Statement stat = conn.createStatement();
stat.execute("create table test(id int primary key, name varchar)");
stat.execute("insert into test select x, 'Hello' from system_range(1, 2)");
conn.setAutoCommit(false);
Connection conn1;
conn1 = getConnection(url);
final AtomicLong updateEnd = new AtomicLong();
final Statement stat1 = conn.createStatement();
Task task = new Task() {
public void call() throws Exception {
while (!stop) {
if (System.currentTimeMillis() < updateEnd.get()) {
stat.execute("update test set name = 'Hallo'");
stat1.execute("checkpoint");
stat.execute("update test set name = 'Hello'");
stat.execute("commit");
stat.execute("checkpoint");
} else {
Thread.sleep(10);
}
}
}
};
Connection conn2;
conn2 = getConnection(url + ";database_event_listener='" + BackupListener.class.getName() + "'");
Statement stat2 = conn2.createStatement();
task.execute();
for (int i = 0; i < 10; i++) {
updateEnd.set(System.currentTimeMillis() + 2000);
stat2.execute("backup to '"+getBaseDir()+"/backup.zip'");
stat2.execute("checkpoint");
Restore.execute(getBaseDir() + "/backup.zip", getBaseDir() + "/t2", "backup", true);
Connection conn3;
conn3 = getConnection("t2/backup");
Statement stat3 = conn3.createStatement();
stat3.execute("script");
ResultSet rs = stat3.executeQuery("select * from test where name='Hallo'");
while (rs.next()) {
fail();
}
conn3.close();
}
task.get();
conn.close();
conn1.close();
conn2.close();
}
/**
* A backup listener to test concurrent backup.
*/
public static class BackupListener implements DatabaseEventListener {
public void closingDatabase() {
// ignore
}
public void exceptionThrown(SQLException e, String sql) {
// ignore
}
public void init(String url) {
// ignore
}
public void opened() {
// ignore
}
public void setProgress(int state, String name, int x, int max) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// ignore
}
if (x % 400 == 0) {
// System.out.println("state: " + state + " name: " + name + " x:" + x + "/" + max);
}
}
}
private void testBackupRestoreLob() throws SQLException { private void testBackupRestoreLob() throws SQLException {
deleteDb("backup"); deleteDb("backup");
Connection conn = getConnection("backup"); Connection conn = getConnection("backup");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论