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