提交 769193eb authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: error codes; fix MVRTreeMap iterators

上级 aed6260d
......@@ -95,6 +95,7 @@ public class Chunk {
static Chunk fromHeader(ByteBuffer buff, long start) {
if (buff.get() != 'c') {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"File corrupt reading chunk at position {0}", start);
}
int length = buff.getInt();
......
......@@ -15,27 +15,19 @@ import java.util.Iterator;
*/
public class Cursor<K> implements Iterator<K> {
protected final MVMap<K, ?> map;
protected final K from;
protected CursorPos pos;
protected K current;
private final MVMap<K, ?> map;
private final K from;
private CursorPos pos;
private K current;
private final Page root;
private boolean initialized;
protected Cursor(MVMap<K, ?> map, Page root, K from) {
Cursor(MVMap<K, ?> map, Page root, K from) {
this.map = map;
this.root = root;
this.from = from;
}
@Override
public K next() {
hasNext();
K c = current;
fetchNext();
return c;
}
@Override
public boolean hasNext() {
if (!initialized) {
......@@ -46,6 +38,14 @@ public class Cursor<K> implements Iterator<K> {
return current != null;
}
@Override
public K next() {
hasNext();
K c = current;
fetchNext();
return c;
}
/**
* Skip over that many entries. This method is relatively fast (for this map
* implementation) even if many entries need to be skipped.
......@@ -82,7 +82,7 @@ public class Cursor<K> implements Iterator<K> {
* @param p the page to start
* @param from the key to search
*/
protected void min(Page p, K from) {
private void min(Page p, K from) {
while (true) {
if (p.isLeaf()) {
int x = from == null ? 0 : p.binarySearch(from);
......@@ -107,7 +107,7 @@ public class Cursor<K> implements Iterator<K> {
* Fetch the next entry if there is one.
*/
@SuppressWarnings("unchecked")
protected void fetchNext() {
private void fetchNext() {
while (pos != null) {
if (pos.index < pos.page.getKeyCount()) {
current = (K) pos.page.getKey(pos.index++);
......
......@@ -24,6 +24,57 @@ import org.h2.util.New;
*/
public class DataUtils {
/**
* An error occurred while reading from the file.
*/
public static final int ERROR_READING_FAILED = 1;
/**
* An error occurred when trying to write to the file.
*/
public static final int ERROR_WRITING_FAILED = 2;
/**
* An internal error occurred. This could be a bug, or a memory corruption
* (for example caused by out of memory).
*/
public static final int ERROR_INTERNAL = 3;
/**
* The object is already closed.
*/
public static final int ERROR_CLOSED = 4;
/**
* The file format is not supported.
*/
public static final int ERROR_UNSUPPORTED_FORMAT = 5;
/**
* The file is corrupt or (for encrypted files) the encryption key is wrong.
*/
public static final int ERROR_FILE_CORRUPT = 6;
/**
* The file is locked.
*/
public static final int ERROR_FILE_LOCKED = 7;
/**
* An error occurred when serializing or de-serializing.
*/
public static final int ERROR_SERIALIZATION = 8;
/**
* The transaction store is corrupt.
*/
public static final int ERROR_TRANSACTION_CORRUPT = 100;
/**
* A lock timeout occurred.
*/
public static final int ERROR_TRANSACTION_LOCK_TIMEOUT = 101;
/**
* The type for leaf page.
*/
......@@ -332,6 +383,7 @@ public class DataUtils {
dst.rewind();
} catch (IOException e) {
throw newIllegalStateException(
ERROR_READING_FAILED,
"Reading from {0} failed; length {1} at {2}",
file, dst.remaining(), pos, e);
}
......@@ -353,6 +405,7 @@ public class DataUtils {
} while (src.remaining() > 0);
} catch (IOException e) {
throw newIllegalStateException(
ERROR_WRITING_FAILED,
"Writing to {0} failed; length {1} at {2}",
file, src.remaining(), pos, e);
}
......@@ -518,13 +571,17 @@ public class DataUtils {
*
* @param s the list
* @return the map
* @throws IllegalStateException if parsing failed
*/
public static HashMap<String, String> parseMap(String s) {
HashMap<String, String> map = New.hashMap();
for (int i = 0, size = s.length(); i < size;) {
int startKey = i;
i = s.indexOf(':', i);
checkArgument(i >= 0, "Not a map");
if (i < 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT, "Not a map: {0}", s);
}
String key = s.substring(startKey, i++);
StringBuilder buff = new StringBuilder();
while (i < size) {
......@@ -596,7 +653,7 @@ public class DataUtils {
public static IllegalArgumentException newIllegalArgumentException(
String message, Object... arguments) {
return initCause(new IllegalArgumentException(
MessageFormat.format(message, arguments) + " " + getVersion()),
formatMessage(0, message, arguments)),
arguments);
}
......@@ -608,16 +665,18 @@ public class DataUtils {
*/
public static UnsupportedOperationException newUnsupportedOperationException(
String message) {
return new UnsupportedOperationException(message + " " + getVersion());
return new UnsupportedOperationException(formatMessage(0, message));
}
/**
* Create a new ConcurrentModificationException.
*
* @param message the message
* @return the exception
*/
public static ConcurrentModificationException newConcurrentModificationException() {
return new ConcurrentModificationException(getVersion());
public static ConcurrentModificationException newConcurrentModificationException(
String message) {
return new ConcurrentModificationException(formatMessage(0, message));
}
/**
......@@ -628,9 +687,9 @@ public class DataUtils {
* @return the exception
*/
public static IllegalStateException newIllegalStateException(
String message, Object... arguments) {
int errorCode, String message, Object... arguments) {
return initCause(new IllegalStateException(
MessageFormat.format(message, arguments) + " " + getVersion()),
formatMessage(errorCode, message, arguments)),
arguments);
}
......@@ -645,9 +704,34 @@ public class DataUtils {
return e;
}
private static String getVersion() {
private static String formatMessage(int errorCode, String message, Object... arguments) {
return MessageFormat.format(message, arguments) + " " + getVersionAndCode(errorCode);
}
private static String getVersionAndCode(int errorCode) {
return "[" + Constants.VERSION_MAJOR + "." +
Constants.VERSION_MINOR + "." + Constants.BUILD_ID + "]";
Constants.VERSION_MINOR + "." + Constants.BUILD_ID + "/" + errorCode + "]";
}
/**
* Get the error code from an exception message.
*
* @param m the message
* @return the error code, or 0 if none
*/
public static int getErrorCode(String m) {
if (m.endsWith("]")) {
int dash = m.lastIndexOf('/');
if (dash >= 0) {
String s = m.substring(dash + 1, m.length() - 1);
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
// no error code
}
}
}
return 0;
}
/**
......
......@@ -53,6 +53,7 @@ public class FreeSpaceList {
}
}
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Could not find a free page to allocate");
}
......@@ -75,10 +76,12 @@ public class FreeSpaceList {
}
if (found == null) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Cannot find spot to mark chunk as used in free list: {0}", c);
}
if (chunkStart + required > found.start + found.length) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Chunk runs over edge of free space: {0}", c);
}
if (found.start == chunkStart) {
......@@ -127,6 +130,7 @@ public class FreeSpaceList {
}
if (found == null) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Cannot find spot to mark chunk as unused in free list: {0}", c);
}
if (chunkStart + required + 1 == found.start) {
......
......@@ -907,7 +907,8 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/
protected void checkOpen() {
if (closed) {
throw DataUtils.newIllegalStateException("This map is closed");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_CLOSED, "This map is closed");
}
}
......@@ -937,7 +938,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
if (writing) {
// try to detect concurrent modification
// on a best-effort basis
throw DataUtils.newConcurrentModificationException();
throw DataUtils.newConcurrentModificationException(getName());
}
}
......@@ -1004,7 +1005,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
public MVMap<K, V> openVersion(long version) {
if (readOnly) {
throw DataUtils.newUnsupportedOperationException(
"This map is read-only - need to call the method on the writable map");
"This map is read-only; need to call the method on the writable map");
}
DataUtils.checkArgument(version >= createVersion,
"Unknown version {0}; this map was created in version is {1}",
......
......@@ -10,6 +10,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
......@@ -436,6 +437,10 @@ public class MVStore {
/**
* Open the store.
*
* @throws IllegalStateException if the file is corrupt, or an exception
* occurred while opening
* @throws IllegalArgumentException if the directory does not exist
*/
void open() {
meta = new MVMapConcurrent<String, String>(StringDataType.INSTANCE, StringDataType.INSTANCE);
......@@ -478,9 +483,11 @@ public class MVStore {
* @return if opening the file was successful, and false if the file could
* not be opened in write mode because the write file format it too
* high (in which case the file can be opened in read-only mode)
* @throw IllegalStateException if the file could not be opened at all
* @throw IllegalStateException if the file could not be opened
* because of an IOException or file format error
*/
private boolean openFile() {
IllegalStateException exception;
try {
log("file open");
FilePath f = FilePath.get(fileName);
......@@ -493,16 +500,19 @@ public class MVStore {
file = new FilePathCrypt.FileCrypt(fileName, password, file);
}
file = FilePathCache.wrap(file);
try {
if (readOnly) {
fileLock = file.tryLock(0, Long.MAX_VALUE, true);
if (fileLock == null) {
throw new IOException("The file is locked: " + fileName);
}
} else {
fileLock = file.tryLock();
if (fileLock == null) {
throw new IOException("The file is locked: " + fileName);
}
} catch (OverlappingFileLockException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_LOCKED, "The file is locked: {0}", fileName, e);
}
if (fileLock == null) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_LOCKED, "The file is locked: {0}", fileName);
}
fileSize = file.size();
if (fileSize == 0) {
......@@ -521,6 +531,7 @@ public class MVStore {
int formatRead = x == null ? formatWrite : Integer.parseInt(x);
if (formatRead > FORMAT_READ) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_UNSUPPORTED_FORMAT,
"The file format {0} is larger than the supported format {1}",
formatRead, FORMAT_READ);
}
......@@ -533,14 +544,21 @@ public class MVStore {
readMeta();
}
}
} catch (Exception e) {
exception = null;
} catch (IOException e) {
exception = DataUtils.newIllegalStateException(
DataUtils.ERROR_READING_FAILED,
"Could not open file {0}", fileName, e);
} catch (IllegalStateException e) {
exception = e;
}
if (exception != null) {
try {
closeFile(false);
} catch (Exception e2) {
// ignore
}
throw DataUtils.newIllegalStateException(
"Could not open file {0}", fileName, e);
throw exception;
}
return true;
}
......@@ -603,7 +621,12 @@ public class MVStore {
for (int i = 0; i < 3 * BLOCK_SIZE; i += BLOCK_SIZE) {
String s = new String(buff.array(), i, BLOCK_SIZE, Constants.UTF8)
.trim();
HashMap<String, String> m = DataUtils.parseMap(s);
HashMap<String, String> m;
try {
m = DataUtils.parseMap(s);
} catch (IllegalArgumentException e) {
continue;
}
String f = m.remove("fletcher");
if (f == null) {
continue;
......@@ -630,7 +653,8 @@ public class MVStore {
}
}
if (currentVersion < 0) {
throw DataUtils.newIllegalStateException("File header is corrupt");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT, "File header is corrupt: {0}", fileName);
}
lastStoredVersion = -1;
}
......@@ -645,8 +669,11 @@ public class MVStore {
int checksum = DataUtils.getFletcher32(bytes, bytes.length / 2 * 2);
DataUtils.appendMap(buff, "fletcher", Integer.toHexString(checksum));
bytes = buff.toString().getBytes(Constants.UTF8);
DataUtils.checkArgument(bytes.length <= BLOCK_SIZE,
if (bytes.length > BLOCK_SIZE) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_UNSUPPORTED_FORMAT,
"File header too large: {0}", buff);
}
return bytes;
}
......@@ -720,6 +747,7 @@ public class MVStore {
maps.clear();
} catch (Exception e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Closing failed for file {0}", fileName, e);
} finally {
file = null;
......@@ -799,7 +827,8 @@ public class MVStore {
return currentVersion;
}
if (readOnly) {
throw DataUtils.newIllegalStateException("This store is read-only");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED, "This store is read-only");
}
int currentUnsavedPageCount = unsavedPageCount;
long storeVersion = currentStoreVersion = currentVersion;
......@@ -893,7 +922,8 @@ public class MVStore {
if (ASSERT) {
if (freedPages.size() > 0) {
throw DataUtils.newIllegalStateException("Temporary freed chunks");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Temporary freed chunks");
}
}
......@@ -1002,14 +1032,17 @@ public class MVStore {
c.pageCountLive += f.pageCountLive;
if (c.pageCountLive < 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Corrupt page count {0}", c.pageCountLive);
}
if (c.maxLengthLive < 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Corrupt max length {0}", c.maxLengthLive);
}
if (c.pageCount == 0 && c.maxLengthLive > 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Corrupt max length {0}", c.maxLengthLive);
}
modified.add(c);
......@@ -1060,6 +1093,7 @@ public class MVStore {
file.truncate(used);
} catch (IOException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Could not truncate file {0} to size {1}",
fileName, used, e);
}
......@@ -1259,6 +1293,7 @@ public class MVStore {
Chunk c = getChunk(pos);
if (c == null) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"Chunk {0} not found",
DataUtils.getPageChunkId(pos));
}
......@@ -1682,7 +1717,8 @@ public class MVStore {
private void checkOpen() {
if (closed) {
throw DataUtils.newIllegalStateException("This store is closed");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_CLOSED, "This store is closed");
}
}
......@@ -1741,6 +1777,15 @@ public class MVStore {
store(true);
}
/**
* Set the read cache size in MB.
*
* @param mb the cache size in MB.
*/
public void setCacheSize(long mb) {
cache.setMaxMemory(mb * 1024 * 1024);
}
public boolean isReadOnly() {
return readOnly;
}
......
......@@ -429,6 +429,7 @@ public class Page {
}
if (check != totalCount) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Expected: {0} got: {1}", check, totalCount);
}
}
......@@ -694,6 +695,7 @@ public class Page {
int pageLength = buff.getInt();
if (pageLength > maxLength) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"File corrupted, expected length =< {0}, got {1}",
maxLength, pageLength);
}
......@@ -701,6 +703,7 @@ public class Page {
int mapId = DataUtils.readVarInt(buff);
if (mapId != map.getId()) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"File corrupted, expected map id {0}, got {1}",
map.getId(), mapId);
}
......@@ -709,6 +712,7 @@ public class Page {
^ DataUtils.getCheckValue(pageLength);
if (check != (short) checkTest) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"File corrupted, expected check value {0}, got {1}",
checkTest, check);
}
......@@ -818,7 +822,8 @@ public class Page {
^ DataUtils.getCheckValue(pageLength);
buff.putShort(start + 4, (short) check);
if (pos != 0) {
throw DataUtils.newIllegalStateException("Page already stored");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Page already stored");
}
pos = DataUtils.getPagePos(chunkId, start, pageLength, type);
long max = DataUtils.getPageMaxLength(pos);
......@@ -901,7 +906,8 @@ public class Page {
public int getMemory() {
if (MVStore.ASSERT) {
if (memory != calculateMemory()) {
throw DataUtils.newIllegalStateException("Memory calculation error");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Memory calculation error");
}
}
return memory;
......
......@@ -11,13 +11,16 @@ import java.util.List;
import org.h2.api.TableEngine;
import org.h2.command.ddl.CreateTableData;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.store.InDoubtTransaction;
import org.h2.store.fs.FileUtils;
import org.h2.table.RegularTable;
import org.h2.table.TableBase;
import org.h2.util.New;
......@@ -27,12 +30,13 @@ import org.h2.util.New;
*/
public class MVTableEngine implements TableEngine {
@Override
public TableBase createTable(CreateTableData data) {
Database db = data.session.getDatabase();
if (!data.persistData || (data.temporary && !data.persistIndexes)) {
return new RegularTable(data);
}
/**
* Initialize the MVStore.
*
* @param db the database
* @return the store
*/
public static Store init(Database db) {
Store store = db.getMvStore();
if (store == null) {
byte[] key = db.getFilePasswordHash();
......@@ -41,9 +45,19 @@ public class MVTableEngine implements TableEngine {
if (dbPath == null) {
store = new Store(db, builder.open());
} else {
builder.fileName(dbPath + Constants.SUFFIX_MV_FILE);
String fileName = dbPath + Constants.SUFFIX_MV_FILE;
builder.fileName(fileName);
if (db.isReadOnly()) {
builder.readOnly();
} else {
// possibly create the directory
boolean exists = FileUtils.exists(fileName);
if (exists && !FileUtils.canWrite(fileName)) {
// read only
} else {
String dir = FileUtils.getParent(fileName);
FileUtils.createDirectories(dir);
}
}
if (key != null) {
char[] password = new char[key.length];
......@@ -52,10 +66,32 @@ public class MVTableEngine implements TableEngine {
}
builder.encryptionKey(password);
}
try {
store = new Store(db, builder.open());
} catch (IllegalStateException e) {
int errorCode = DataUtils.getErrorCode(e.getMessage());
if (errorCode == DataUtils.ERROR_FILE_CORRUPT) {
if (key != null) {
throw DbException.get(ErrorCode.FILE_ENCRYPTION_ERROR_1, fileName);
}
} else if (errorCode == DataUtils.ERROR_FILE_LOCKED) {
throw DbException.get(ErrorCode.DATABASE_ALREADY_OPEN_1, fileName);
}
throw DbException.get(ErrorCode.FILE_CORRUPTED_1, fileName);
}
}
db.setMvStore(store);
}
return store;
}
@Override
public TableBase createTable(CreateTableData data) {
Database db = data.session.getDatabase();
if (!data.persistData || (data.temporary && !data.persistIndexes)) {
return new RegularTable(data);
}
Store store = init(db);
MVTable table = new MVTable(data, store);
store.openTables.add(table);
table.init(data.session);
......@@ -188,6 +224,10 @@ public class MVTableEngine implements TableEngine {
return result;
}
public void setCacheSize(int kb) {
store.setCacheSize(kb * 1024);
}
}
/**
......
......@@ -111,7 +111,9 @@ public class TransactionStore {
}
Long lastKey = preparedTransactions.lastKey();
if (lastKey != null && lastKey.longValue() > lastTransactionId) {
throw DataUtils.newIllegalStateException("Last transaction not stored");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TRANSACTION_CORRUPT,
"Last transaction not stored");
}
if (undoLog.size() > 0) {
long[] key = undoLog.firstKey();
......@@ -616,7 +618,8 @@ public class TransactionStore {
*/
void checkNotClosed() {
if (status == STATUS_CLOSED) {
throw DataUtils.newIllegalStateException("Transaction is closed");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_CLOSED, "Transaction is closed");
}
}
......@@ -749,14 +752,16 @@ public class TransactionStore {
// wait until it is committed, or until the lock timeout
long timeout = transaction.store.lockTimeout;
if (timeout == 0) {
throw DataUtils.newIllegalStateException("Lock timeout");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TRANSACTION_LOCK_TIMEOUT, "Lock timeout");
}
if (start == 0) {
start = System.currentTimeMillis();
} else {
long t = System.currentTimeMillis() - start;
if (t > timeout) {
throw DataUtils.newIllegalStateException("Lock timeout");
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TRANSACTION_LOCK_TIMEOUT, "Lock timeout");
}
// TODO use wait/notify instead, or remove the feature
try {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论