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

New experimental database file locking mechanism "FS" to use native file locking.

上级 d6517168
...@@ -507,7 +507,7 @@ public class Database implements DataHandler { ...@@ -507,7 +507,7 @@ public class Database implements DataHandler {
traceSystem.getTrace(Trace.DATABASE) traceSystem.getTrace(Trace.DATABASE)
.info("opening " + databaseName + " (build " + Constants.BUILD_ID + ")"); .info("opening " + databaseName + " (build " + Constants.BUILD_ID + ")");
if (autoServerMode) { if (autoServerMode) {
if (readOnly || fileLockMethod == FileLock.LOCK_NO || fileLockMethod == FileLock.LOCK_SERIALIZED) { if (readOnly || fileLockMethod == FileLock.LOCK_NO || fileLockMethod == FileLock.LOCK_SERIALIZED || fileLockMethod == FileLock.LOCK_FS) {
throw DbException.getUnsupportedException("autoServerMode && (readOnly || fileLockMethod == NO" + throw DbException.getUnsupportedException("autoServerMode && (readOnly || fileLockMethod == NO" +
" || fileLockMethod == SERIALIZED)"); " || fileLockMethod == SERIALIZED)");
} }
...@@ -519,12 +519,14 @@ public class Database implements DataHandler { ...@@ -519,12 +519,14 @@ public class Database implements DataHandler {
} }
} }
if (!readOnly && fileLockMethod != FileLock.LOCK_NO) { if (!readOnly && fileLockMethod != FileLock.LOCK_NO) {
if (fileLockMethod != FileLock.LOCK_FS) {
lock = new FileLock(traceSystem, lockFileName, Constants.LOCK_SLEEP); lock = new FileLock(traceSystem, lockFileName, Constants.LOCK_SLEEP);
lock.lock(fileLockMethod); lock.lock(fileLockMethod);
if (autoServerMode) { if (autoServerMode) {
startServer(lock.getUniqueId()); startServer(lock.getUniqueId());
} }
} }
}
while (isReconnectNeeded() && !beforeWriting()) { while (isReconnectNeeded() && !beforeWriting()) {
// wait until others stopped writing and // wait until others stopped writing and
// until we can write (file are not open - no need to re-connect) // until we can write (file are not open - no need to re-connect)
...@@ -1142,7 +1144,7 @@ public class Database implements DataHandler { ...@@ -1142,7 +1144,7 @@ public class Database implements DataHandler {
} }
reconnectModified(false); reconnectModified(false);
closeFiles(); closeFiles();
if (persistent && lock == null && fileLockMethod != FileLock.LOCK_NO) { if (persistent && lock == null && fileLockMethod != FileLock.LOCK_NO && fileLockMethod != FileLock.LOCK_FS) {
// everything already closed (maybe in checkPowerOff) // everything already closed (maybe in checkPowerOff)
// don't delete temp files in this case because // don't delete temp files in this case because
// the database could be open now (even from within another process) // the database could be open now (even from within another process)
...@@ -2056,6 +2058,9 @@ public class Database implements DataHandler { ...@@ -2056,6 +2058,9 @@ public class Database implements DataHandler {
if (pageSize != SysProperties.PAGE_SIZE) { if (pageSize != SysProperties.PAGE_SIZE) {
pageStore.setPageSize(pageSize); pageStore.setPageSize(pageSize);
} }
if (!readOnly && fileLockMethod == FileLock.LOCK_FS) {
pageStore.setLockFile(true);
}
pageStore.open(); pageStore.open();
} }
return pageStore; return pageStore;
......
...@@ -57,6 +57,11 @@ public class FileLock implements Runnable { ...@@ -57,6 +57,11 @@ public class FileLock implements Runnable {
*/ */
public static final int LOCK_SERIALIZED = 3; public static final int LOCK_SERIALIZED = 3;
/**
* Use the file system to lock the file; don't use a separate lock file.
*/
public static final int LOCK_FS = 4;
private static final String MAGIC = "FileLock"; private static final String MAGIC = "FileLock";
private static final String FILE = "file", SOCKET = "socket", SERIALIZED = "serialized"; private static final String FILE = "file", SOCKET = "socket", SERIALIZED = "serialized";
private static final int RANDOM_BYTES = 16; private static final int RANDOM_BYTES = 16;
...@@ -134,6 +139,8 @@ public class FileLock implements Runnable { ...@@ -134,6 +139,8 @@ public class FileLock implements Runnable {
case LOCK_SERIALIZED: case LOCK_SERIALIZED:
lockSerialized(); lockSerialized();
break; break;
case LOCK_FS:
break;
} }
locked = true; locked = true;
} }
...@@ -462,6 +469,8 @@ public class FileLock implements Runnable { ...@@ -462,6 +469,8 @@ public class FileLock implements Runnable {
return FileLock.LOCK_SOCKET; return FileLock.LOCK_SOCKET;
} else if (method.equalsIgnoreCase("SERIALIZED")) { } else if (method.equalsIgnoreCase("SERIALIZED")) {
return FileLock.LOCK_SERIALIZED; return FileLock.LOCK_SERIALIZED;
} else if (method.equalsIgnoreCase("FS")) {
return FileLock.LOCK_FS;
} else { } else {
throw DbException.get(ErrorCode.UNSUPPORTED_LOCK_METHOD_1, method); throw DbException.get(ErrorCode.UNSUPPORTED_LOCK_METHOD_1, method);
} }
......
...@@ -509,4 +509,21 @@ public class FileStore { ...@@ -509,4 +509,21 @@ public class FileStore {
return textMode; return textMode;
} }
/**
* Try to lock the file.
*
* @return true if successful
*/
public boolean tryLock() {
return file.tryLock();
}
/**
* Release the file lock.
*/
public void releaseLock() {
file.releaseLock();
}
} }
...@@ -184,6 +184,7 @@ public class PageStore implements CacheWriter { ...@@ -184,6 +184,7 @@ public class PageStore implements CacheWriter {
private long logSizeBase; private long logSizeBase;
private HashMap<String, Integer> statistics; private HashMap<String, Integer> statistics;
private int logMode = LOG_MODE_SYNC; private int logMode = LOG_MODE_SYNC;
private boolean lockFile;
/** /**
* Create a new page store object. * Create a new page store object.
...@@ -281,6 +282,7 @@ public class PageStore implements CacheWriter { ...@@ -281,6 +282,7 @@ public class PageStore implements CacheWriter {
setPageSize(pageSize); setPageSize(pageSize);
freeListPagesPerList = PageFreeList.getPagesAddressed(pageSize); freeListPagesPerList = PageFreeList.getPagesAddressed(pageSize);
file = database.openFile(fileName, accessMode, false); file = database.openFile(fileName, accessMode, false);
lockFile();
recoveryRunning = true; recoveryRunning = true;
writeStaticHeader(); writeStaticHeader();
writeVariableHeader(); writeVariableHeader();
...@@ -294,8 +296,17 @@ public class PageStore implements CacheWriter { ...@@ -294,8 +296,17 @@ public class PageStore implements CacheWriter {
increaseFileSize(); increaseFileSize();
} }
private void lockFile() {
if (lockFile) {
if (!file.tryLock()) {
throw DbException.get(ErrorCode.DATABASE_ALREADY_OPEN_1, fileName);
}
}
}
private void openExisting() { private void openExisting() {
file = database.openFile(fileName, accessMode, true); file = database.openFile(fileName, accessMode, true);
lockFile();
readStaticHeader(); readStaticHeader();
freeListPagesPerList = PageFreeList.getPagesAddressed(pageSize); freeListPagesPerList = PageFreeList.getPagesAddressed(pageSize);
fileLength = file.length(); fileLength = file.length();
...@@ -304,6 +315,7 @@ public class PageStore implements CacheWriter { ...@@ -304,6 +315,7 @@ public class PageStore implements CacheWriter {
if (database.isReadOnly()) { if (database.isReadOnly()) {
throw DbException.get(ErrorCode.FILE_CORRUPTED_1, fileName + " pageCount: " + pageCount); throw DbException.get(ErrorCode.FILE_CORRUPTED_1, fileName + " pageCount: " + pageCount);
} }
file.releaseLock();
file.close(); file.close();
IOUtils.delete(fileName); IOUtils.delete(fileName);
openNew(); openNew();
...@@ -800,6 +812,7 @@ public class PageStore implements CacheWriter { ...@@ -800,6 +812,7 @@ public class PageStore implements CacheWriter {
} }
if (file != null) { if (file != null) {
try { try {
file.releaseLock();
file.close(); file.close();
} finally { } finally {
file = null; file = null;
...@@ -1304,7 +1317,7 @@ public class PageStore implements CacheWriter { ...@@ -1304,7 +1317,7 @@ public class PageStore implements CacheWriter {
void redoDelete(int logPos, int tableId, long key) { void redoDelete(int logPos, int tableId, long key) {
Index index = metaObjects.get(tableId); Index index = metaObjects.get(tableId);
PageDataIndex scan = (PageDataIndex) index; PageDataIndex scan = (PageDataIndex) index;
Row row = scan.getRow(key); Row row = scan.getRowWithKey(key);
redo(logPos, tableId, row, false); redo(logPos, tableId, row, false);
} }
...@@ -1729,4 +1742,8 @@ public class PageStore implements CacheWriter { ...@@ -1729,4 +1742,8 @@ public class PageStore implements CacheWriter {
return logMode; return logMode;
} }
public void setLockFile(boolean lockFile) {
this.lockFile = lockFile;
}
} }
...@@ -75,4 +75,16 @@ public interface FileObject { ...@@ -75,4 +75,16 @@ public interface FileObject {
*/ */
String getName(); String getName();
/**
* Try to lock the file exclusively.
*
* @return true if locking was successful
*/
boolean tryLock();
/**
* Release the file lock.
*/
void releaseLock();
} }
...@@ -9,6 +9,7 @@ package org.h2.store.fs; ...@@ -9,6 +9,7 @@ package org.h2.store.fs;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
...@@ -18,6 +19,7 @@ import org.h2.util.IOUtils; ...@@ -18,6 +19,7 @@ import org.h2.util.IOUtils;
public class FileObjectDisk extends RandomAccessFile implements FileObject { public class FileObjectDisk extends RandomAccessFile implements FileObject {
private final String name; private final String name;
private FileLock lock;
FileObjectDisk(String fileName, String mode) throws FileNotFoundException { FileObjectDisk(String fileName, String mode) throws FileNotFoundException {
super(fileName, mode); super(fileName, mode);
...@@ -47,4 +49,27 @@ public class FileObjectDisk extends RandomAccessFile implements FileObject { ...@@ -47,4 +49,27 @@ public class FileObjectDisk extends RandomAccessFile implements FileObject {
return name; return name;
} }
public synchronized boolean tryLock() {
if (lock == null) {
try {
lock = getChannel().tryLock();
} catch (Exception e) {
// could not lock (OverlappingFileLockException)
}
return lock != null;
}
return false;
}
public synchronized void releaseLock() {
if (lock != null) {
try {
lock.release();
} catch (IOException e) {
// ignore
}
lock = null;
}
}
} }
...@@ -11,6 +11,7 @@ import java.io.IOException; ...@@ -11,6 +11,7 @@ import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
/** /**
* File which uses NIO FileChannel. * File which uses NIO FileChannel.
...@@ -19,6 +20,7 @@ public class FileObjectDiskChannel implements FileObject { ...@@ -19,6 +20,7 @@ public class FileObjectDiskChannel implements FileObject {
private final String name; private final String name;
private FileChannel channel; private FileChannel channel;
private FileLock lock;
FileObjectDiskChannel(String fileName, String mode) throws FileNotFoundException { FileObjectDiskChannel(String fileName, String mode) throws FileNotFoundException {
this.name = fileName; this.name = fileName;
...@@ -86,4 +88,27 @@ public class FileObjectDiskChannel implements FileObject { ...@@ -86,4 +88,27 @@ public class FileObjectDiskChannel implements FileObject {
channel.write(buf); channel.write(buf);
} }
public synchronized boolean tryLock() {
if (lock == null) {
try {
lock = channel.tryLock();
} catch (IOException e) {
// could not lock
}
return lock != null;
}
return false;
}
public synchronized void releaseLock() {
if (lock != null) {
try {
lock.release();
} catch (IOException e) {
// ignore
}
lock = null;
}
}
} }
...@@ -13,6 +13,7 @@ import java.lang.ref.WeakReference; ...@@ -13,6 +13,7 @@ import java.lang.ref.WeakReference;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.BufferUnderflowException; import java.nio.BufferUnderflowException;
import java.nio.MappedByteBuffer; import java.nio.MappedByteBuffer;
import java.nio.channels.FileLock;
import java.nio.channels.FileChannel.MapMode; import java.nio.channels.FileChannel.MapMode;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
...@@ -28,6 +29,7 @@ public class FileObjectDiskMapped implements FileObject { ...@@ -28,6 +29,7 @@ public class FileObjectDiskMapped implements FileObject {
private final MapMode mode; private final MapMode mode;
private RandomAccessFile file; private RandomAccessFile file;
private MappedByteBuffer mapped; private MappedByteBuffer mapped;
private FileLock lock;
/** /**
* The position within the file. Can't use the position of the mapped buffer * The position within the file. Can't use the position of the mapped buffer
...@@ -180,4 +182,27 @@ public class FileObjectDiskMapped implements FileObject { ...@@ -180,4 +182,27 @@ public class FileObjectDiskMapped implements FileObject {
pos += len; pos += len;
} }
public synchronized boolean tryLock() {
if (lock == null) {
try {
lock = file.getChannel().tryLock();
} catch (IOException e) {
// could not lock
}
return lock != null;
}
return false;
}
public synchronized void releaseLock() {
if (lock != null) {
try {
lock.release();
} catch (IOException e) {
// ignore
}
lock = null;
}
}
} }
...@@ -69,4 +69,12 @@ public class FileObjectMemory implements FileObject { ...@@ -69,4 +69,12 @@ public class FileObjectMemory implements FileObject {
return data.getLastModified(); return data.getLastModified();
} }
public boolean tryLock() {
return data.tryLock();
}
public void releaseLock() {
data.releaseLock();
}
} }
...@@ -37,6 +37,7 @@ class FileObjectMemoryData { ...@@ -37,6 +37,7 @@ class FileObjectMemoryData {
private byte[][] data; private byte[][] data;
private long lastModified; private long lastModified;
private boolean isReadOnly; private boolean isReadOnly;
private volatile boolean locked;
static { static {
byte[] n = new byte[BLOCK_SIZE]; byte[] n = new byte[BLOCK_SIZE];
...@@ -295,4 +296,24 @@ class FileObjectMemoryData { ...@@ -295,4 +296,24 @@ class FileObjectMemoryData {
return true; return true;
} }
/**
* Lock the file.
*
* @return if successful
*/
synchronized boolean tryLock() {
if (locked) {
return false;
}
locked = true;
return true;
}
/**
* Unlock the file.
*/
public synchronized void releaseLock() {
locked = false;
}
} }
...@@ -152,4 +152,12 @@ public class FileObjectSplit implements FileObject { ...@@ -152,4 +152,12 @@ public class FileObjectSplit implements FileObject {
return name; return name;
} }
public boolean tryLock() {
return list[0].tryLock();
}
public void releaseLock() {
list[0].releaseLock();
}
} }
...@@ -107,4 +107,12 @@ public class FileObjectZip implements FileObject { ...@@ -107,4 +107,12 @@ public class FileObjectZip implements FileObject {
return file.getName(); return file.getName();
} }
public boolean tryLock() {
return false;
}
public void releaseLock() {
// ignore
}
} }
...@@ -11,7 +11,6 @@ import java.sql.DriverManager; ...@@ -11,7 +11,6 @@ import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.store.fs.FileSystem;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.test.utils.DebugFileSystem; import org.h2.test.utils.DebugFileSystem;
...@@ -32,8 +31,7 @@ public class TestPowerOffFs extends TestBase { ...@@ -32,8 +31,7 @@ public class TestPowerOffFs extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
DebugFileSystem.register(); fs = DebugFileSystem.register();
fs = (DebugFileSystem) FileSystem.getInstance("debug:/");
test(Integer.MAX_VALUE); test(Integer.MAX_VALUE);
System.out.println(Integer.MAX_VALUE - fs.getPowerOffCount()); System.out.println(Integer.MAX_VALUE - fs.getPowerOffCount());
System.out.println("done"); System.out.println("done");
......
...@@ -14,7 +14,6 @@ import java.sql.Statement; ...@@ -14,7 +14,6 @@ import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Random; import java.util.Random;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.store.fs.FileSystem;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.test.utils.DebugFileSystem; import org.h2.test.utils.DebugFileSystem;
import org.h2.util.New; import org.h2.util.New;
...@@ -44,9 +43,8 @@ public class TestPowerOffFs2 extends TestBase { ...@@ -44,9 +43,8 @@ public class TestPowerOffFs2 extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
DebugFileSystem.register(); fs = DebugFileSystem.register();
url = "jdbc:h2:debug:memFS:powerOffFs;FILE_LOCK=NO;TRACE_LEVEL_FILE=0;WRITE_DELAY=0;CACHE_SIZE=32"; url = "jdbc:h2:debug:memFS:powerOffFs;FILE_LOCK=NO;TRACE_LEVEL_FILE=0;WRITE_DELAY=0;CACHE_SIZE=32";
fs = (DebugFileSystem) FileSystem.getInstance("debug:/");
for (int i = 0;; i++) { for (int i = 0;; i++) {
test(i); test(i);
} }
......
...@@ -90,4 +90,12 @@ public class FileObjectDatabase implements FileObject { ...@@ -90,4 +90,12 @@ public class FileObjectDatabase implements FileObject {
return fileName; return fileName;
} }
public void releaseLock() {
// ignore
}
public boolean tryLock() {
return false;
}
} }
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
package org.h2.test.unit; package org.h2.test.unit;
import java.io.File; import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.message.TraceSystem; import org.h2.message.TraceSystem;
import org.h2.store.FileLock; import org.h2.store.FileLock;
...@@ -50,12 +54,26 @@ public class TestFileLock extends TestBase implements Runnable { ...@@ -50,12 +54,26 @@ public class TestFileLock extends TestBase implements Runnable {
if (!getFile().startsWith(TestBase.BASE_TEST_DIR)) { if (!getFile().startsWith(TestBase.BASE_TEST_DIR)) {
return; return;
} }
testFsFileLock();
testFutureModificationDate(); testFutureModificationDate();
testSimple(); testSimple();
test(false); test(false);
test(true); test(true);
} }
private void testFsFileLock() throws Exception {
deleteDb("fileLock");
String url = "jdbc:h2:" + getBaseDir() + "/fileLock;FILE_LOCK=FS;OPEN_NEW=TRUE";
Connection conn = DriverManager.getConnection(url);
try {
DriverManager.getConnection(url);
fail();
} catch (SQLException e) {
assertEquals(ErrorCode.DATABASE_ALREADY_OPEN_1, e.getErrorCode());
}
conn.close();
}
private void testFutureModificationDate() throws Exception { private void testFutureModificationDate() throws Exception {
File f = new File(getFile()); File f = new File(getFile());
f.delete(); f.delete();
......
...@@ -38,6 +38,7 @@ public class TestPageStore extends TestBase implements DatabaseEventListener { ...@@ -38,6 +38,7 @@ public class TestPageStore extends TestBase implements DatabaseEventListener {
} }
public void test() throws Exception { public void test() throws Exception {
testInsertReverse();
testInsertDelete(); testInsertDelete();
testCheckpoint(); testCheckpoint();
testDropRecreate(); testDropRecreate();
...@@ -64,6 +65,19 @@ public class TestPageStore extends TestBase implements DatabaseEventListener { ...@@ -64,6 +65,19 @@ public class TestPageStore extends TestBase implements DatabaseEventListener {
deleteDb("pageStore"); deleteDb("pageStore");
} }
private void testInsertReverse() throws SQLException {
deleteDb("pageStore");
Connection conn;
conn = getConnection("pageStore");
Statement stat = conn.createStatement();
stat.execute("create table test(id int primary key, data varchar)");
stat.execute("insert into test select -x, space(100) from system_range(1, 1000)");
stat.execute("drop table test");
stat.execute("create table test(id int primary key, data varchar)");
stat.execute("insert into test select -x, space(2048) from system_range(1, 1000)");
conn.close();
}
private void testInsertDelete() { private void testInsertDelete() {
Row[] x = new Row[0]; Row[] x = new Row[0];
Row r = new Row(null, 0); Row r = new Row(null, 0);
......
...@@ -87,4 +87,15 @@ public class DebugFileObject implements FileObject { ...@@ -87,4 +87,15 @@ public class DebugFileObject implements FileObject {
throw e; throw e;
} }
} }
public boolean tryLock() {
debug("tryLock");
return file.tryLock();
}
public void releaseLock() {
debug("releaseLock");
file.releaseLock();
}
} }
...@@ -33,8 +33,9 @@ public class DebugFileSystem extends FileSystem { ...@@ -33,8 +33,9 @@ public class DebugFileSystem extends FileSystem {
/** /**
* Register the file system. * Register the file system.
*/ */
public static void register() { public static DebugFileSystem register() {
FileSystem.register(INSTANCE); FileSystem.register(INSTANCE);
return INSTANCE;
} }
/** /**
......
...@@ -67,4 +67,12 @@ public class RecordingFileObject implements FileObject { ...@@ -67,4 +67,12 @@ public class RecordingFileObject implements FileObject {
fs.log(Recorder.WRITE, name, buff, file.getFilePointer()); fs.log(Recorder.WRITE, name, buff, file.getFilePointer());
} }
public boolean tryLock() {
return file.tryLock();
}
public void releaseLock() {
file.releaseLock();
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论