提交 900b5667 authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: simpler API

上级 e845504a
...@@ -23,6 +23,7 @@ Change Log ...@@ -23,6 +23,7 @@ Change Log
</li><li>Issue 73: MySQL compatibility: support REPLACE, patch by Cemo Koc. </li><li>Issue 73: MySQL compatibility: support REPLACE, patch by Cemo Koc.
</li><li>The spatial index now works in MVCC mode when using the MVStore storage. </li><li>The spatial index now works in MVCC mode when using the MVStore storage.
</li><li>MVStore: concurrency problems have been fixed. </li><li>MVStore: concurrency problems have been fixed.
The API has been simplified.
</li><li>Improve error message when dropping an index that belongs to a constraint, </li><li>Improve error message when dropping an index that belongs to a constraint,
specify constraint in error message. specify constraint in error message.
</li><li>Issue 518: java.sql.Connection.commit() freezes after LOB modification with EXCLUSIVE connection </li><li>Issue 518: java.sql.Connection.commit() freezes after LOB modification with EXCLUSIVE connection
......
...@@ -59,7 +59,7 @@ TransactionStore: ...@@ -59,7 +59,7 @@ TransactionStore:
MVStore: MVStore:
- automated 'kill process' and 'power failure' test - automated 'kill process' and 'power failure' test
- update checkstyle - update checkstyle
- auto-compact from time to time and on close - feature to auto-compact from time to time and on close
- test and possibly improve compact operation (for large dbs) - test and possibly improve compact operation (for large dbs)
- possibly split chunk metadata into immutable and mutable - possibly split chunk metadata into immutable and mutable
- compact: avoid processing pages using a counting bloom filter - compact: avoid processing pages using a counting bloom filter
...@@ -115,6 +115,19 @@ MVStore: ...@@ -115,6 +115,19 @@ MVStore:
- support log structured merge style operations (blind writes) - support log structured merge style operations (blind writes)
using one map per level plus bloom filter using one map per level plus bloom filter
- have a strict call order MVStore -> MVMap -> Page -> FileStore - have a strict call order MVStore -> MVMap -> Page -> FileStore
- autocommit mode (default) and manual mode
- manual mode: combine commit and store;
rollback only to chunk
- rename writeDelay to commitDelay, default 1 s
- rollback() to rollback to the latest commit; throws exception
in autocommit mode
- fix documentation (including examples)
- autocommit commits, stores, and compacts from time to time;
the background thread should wait at least 90% of the
configured write delay to store changes
- currently, uncommitted changes are stored if there are many transient changes,
and rolled back when opening - is this really needed?
- compact* should also store uncommitted changes (if there are any)
*/ */
...@@ -141,6 +154,7 @@ public class MVStore { ...@@ -141,6 +154,7 @@ public class MVStore {
* The background thread, if any. * The background thread, if any.
*/ */
volatile Thread backgroundThread; volatile Thread backgroundThread;
final Object backgroundThreadSync = new Object();
private volatile boolean reuseSpace = true; private volatile boolean reuseSpace = true;
...@@ -176,9 +190,9 @@ public class MVStore { ...@@ -176,9 +190,9 @@ public class MVStore {
new ConcurrentHashMap<Long, HashMap<Integer, Chunk>>(); new ConcurrentHashMap<Long, HashMap<Integer, Chunk>>();
/** /**
* The metadata map. * The metadata map. Write access to this map needs to be synchronized on the store.
*/ */
private MVMapConcurrent<String, String> meta; private MVMap<String, String> meta;
private final ConcurrentHashMap<Integer, MVMap<?, ?>> maps = private final ConcurrentHashMap<Integer, MVMap<?, ?>> maps =
new ConcurrentHashMap<Integer, MVMap<?, ?>>(); new ConcurrentHashMap<Integer, MVMap<?, ?>>();
...@@ -208,6 +222,12 @@ public class MVStore { ...@@ -208,6 +222,12 @@ public class MVStore {
*/ */
private long lastStoredVersion; private long lastStoredVersion;
/**
* The estimated number of unsaved pages
* (this number may not be completely accurate,
* because it may be changed concurrently, and
* because temporary pages are counted)
*/
private int unsavedPageCount; private int unsavedPageCount;
private int unsavedPageCountMax; private int unsavedPageCountMax;
private boolean storeNeeded; private boolean storeNeeded;
...@@ -220,11 +240,6 @@ public class MVStore { ...@@ -220,11 +240,6 @@ public class MVStore {
private long lastStoreTime; private long lastStoreTime;
/**
* To which version to roll back when opening the store after a crash.
*/
private long lastCommittedVersion;
/** /**
* The earliest chunk to retain, if any. * The earliest chunk to retain, if any.
*/ */
...@@ -235,6 +250,8 @@ public class MVStore { ...@@ -235,6 +250,8 @@ public class MVStore {
*/ */
private volatile long currentStoreVersion = -1; private volatile long currentStoreVersion = -1;
private Thread currentStoreThread;
private volatile boolean metaChanged; private volatile boolean metaChanged;
/** /**
...@@ -256,7 +273,7 @@ public class MVStore { ...@@ -256,7 +273,7 @@ public class MVStore {
pageSplitSize = o == null ? 6 * 1024 : (Integer) o; pageSplitSize = o == null ? 6 * 1024 : (Integer) o;
o = config.get("backgroundExceptionHandler"); o = config.get("backgroundExceptionHandler");
this.backgroundExceptionHandler = (UncaughtExceptionHandler) o; this.backgroundExceptionHandler = (UncaughtExceptionHandler) o;
meta = new MVMapConcurrent<String, String>(StringDataType.INSTANCE, StringDataType.INSTANCE); meta = new MVMap<String, String>(StringDataType.INSTANCE, StringDataType.INSTANCE);
HashMap<String, String> c = New.hashMap(); HashMap<String, String> c = New.hashMap();
c.put("id", "0"); c.put("id", "0");
c.put("createVersion", Long.toString(currentVersion)); c.put("createVersion", Long.toString(currentVersion));
...@@ -320,10 +337,6 @@ public class MVStore { ...@@ -320,10 +337,6 @@ public class MVStore {
readMeta(); readMeta();
} }
} }
long rollback = DataUtils.parseLong(meta.get("rollbackOnOpen"), -1);
if (rollback != -1) {
rollbackTo(rollback);
}
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
try { try {
closeStore(false); closeStore(false);
...@@ -337,7 +350,6 @@ public class MVStore { ...@@ -337,7 +350,6 @@ public class MVStore {
} }
} }
lastStoreTime = getTime(); lastStoreTime = getTime();
this.lastCommittedVersion = currentVersion;
// setWriteDelay starts the thread, but only if // setWriteDelay starts the thread, but only if
// the parameter is different than the current value // the parameter is different than the current value
...@@ -403,7 +415,7 @@ public class MVStore { ...@@ -403,7 +415,7 @@ public class MVStore {
* @param builder the map builder * @param builder the map builder
* @return the map * @return the map
*/ */
public <M extends MVMap<K, V>, K, V> M openMap(String name, MVMap.MapBuilder<M, K, V> builder) { public synchronized <M extends MVMap<K, V>, K, V> M openMap(String name, MVMap.MapBuilder<M, K, V> builder) {
checkOpen(); checkOpen();
String x = meta.get("name." + name); String x = meta.get("name." + name);
int id; int id;
...@@ -445,17 +457,17 @@ public class MVStore { ...@@ -445,17 +457,17 @@ public class MVStore {
* Get the metadata map. This data is for informational purposes only. The * Get the metadata map. This data is for informational purposes only. The
* data is subject to change in future versions. * data is subject to change in future versions.
* <p> * <p>
* The data should not be modified (doing so may corrupt the store). Writing * The data in this map should not be modified (changing system data may
* to it is not always detected as a modification, so that changes to it * corrupt the store). If modifications are needed, they need be
* might not be stored. * synchronized on the store.
* <p> * <p>
* It contains the following entries: * The metadata map contains the following entries:
*
* <pre> * <pre>
* chunk.{chunkId} = {chunk metadata}
* name.{name} = {mapId} * name.{name} = {mapId}
* map.{mapId} = {map metadata} * map.{mapId} = {map metadata}
* root.{mapId} = {root position} * root.{mapId} = {root position}
* chunk.{chunkId} = {chunk metadata} * setting.storeVersion = {version}
* </pre> * </pre>
* *
* @return the metadata map * @return the metadata map
...@@ -636,9 +648,8 @@ public class MVStore { ...@@ -636,9 +648,8 @@ public class MVStore {
} }
/** /**
* Close the file and the store. If there are any committed but unsaved * Close the file and the store. If there are any uncommitted changes, they
* changes, they are written to disk first. If any temporary data was * are written to disk first. All open maps are closed.
* written but not committed, this is rolled back. All open maps are closed.
* <p> * <p>
* It is not allowed to concurrently call close and store. * It is not allowed to concurrently call close and store.
*/ */
...@@ -648,18 +659,8 @@ public class MVStore { ...@@ -648,18 +659,8 @@ public class MVStore {
} }
if (fileStore != null && !fileStore.isReadOnly()) { if (fileStore != null && !fileStore.isReadOnly()) {
stopBackgroundThread(); stopBackgroundThread();
if (hasUnsavedChanges()) {
if (currentStoreVersion >= 0) { store();
// in this case, store is called manually in another thread
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Can not close while storing");
}
if (hasUnsavedChanges() || lastCommittedVersion != currentVersion) {
rollbackTo(lastCommittedVersion);
metaChanged = true;
store(false);
} }
} }
closeStore(true); closeStore(true);
...@@ -739,17 +740,6 @@ public class MVStore { ...@@ -739,17 +740,6 @@ public class MVStore {
return c; return c;
} }
/**
* Increment the current version, without committing the changes.
*
* @return the new version
*/
public long incrementVersion() {
long v = ++currentVersion;
setWriteVersion(v);
return v;
}
private void setWriteVersion(long version) { private void setWriteVersion(long version) {
for (MVMap<?, ?> map : maps.values()) { for (MVMap<?, ?> map : maps.values()) {
map.setWriteVersion(version); map.setWriteVersion(version);
...@@ -768,11 +758,11 @@ public class MVStore { ...@@ -768,11 +758,11 @@ public class MVStore {
* @return the new version * @return the new version
*/ */
public long commit() { public long commit() {
long v = incrementVersion(); if (fileStore != null) {
lastCommittedVersion = v; return store();
if (writeDelay == 0) {
store(false);
} }
long v = ++currentVersion;
setWriteVersion(v);
return v; return v;
} }
...@@ -781,29 +771,17 @@ public class MVStore { ...@@ -781,29 +771,17 @@ public class MVStore {
* there are no unsaved changes, otherwise it increments the current version * there are no unsaved changes, otherwise it increments the current version
* and stores the data (for file based stores). * and stores the data (for file based stores).
* <p> * <p>
* One store operation may run at any time. * At most one store operation may run at any time.
* *
* @return the new version (incremented if there were changes) * @return the new version (incremented if there were changes)
*/ */
public long store() { public synchronized long store() {
checkOpen();
return store(false);
}
/**
* Store changes. Changes that are marked as temporary are rolled back after
* a restart.
*
* @param temp whether the changes are only temporary (not committed), and
* should be rolled back after a crash
* @return the new version (incremented if there were changes)
*/
private synchronized long store(boolean temp) {
if (closed) { if (closed) {
return currentVersion; return currentVersion;
} }
if (fileStore == null) { if (fileStore == null) {
return incrementVersion(); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED, "This is an in-memory store");
} }
if (currentStoreVersion >= 0) { if (currentStoreVersion >= 0) {
// store is possibly called within store, if the meta map changed // store is possibly called within store, if the meta map changed
...@@ -818,37 +796,24 @@ public class MVStore { ...@@ -818,37 +796,24 @@ public class MVStore {
} }
try { try {
currentStoreVersion = currentVersion; currentStoreVersion = currentVersion;
return storeNow(temp); currentStoreThread = Thread.currentThread();
return storeNow();
} finally { } finally {
// in any case reset the current store version, // in any case reset the current store version,
// to allow closing the store // to allow closing the store
currentStoreVersion = -1; currentStoreVersion = -1;
currentStoreThread = null;
} }
} }
private long storeNow(boolean temp) { private long storeNow() {
int currentUnsavedPageCount = unsavedPageCount; int currentUnsavedPageCount = unsavedPageCount;
long storeVersion = currentStoreVersion; long storeVersion = currentStoreVersion;
long version = ++currentVersion; long version = ++currentVersion;
setWriteVersion(version);
long time = getTime(); long time = getTime();
lastStoreTime = time; lastStoreTime = time;
if (temp) {
meta.put("rollbackOnOpen", Long.toString(lastCommittedVersion));
// find the oldest chunk to retain
long minVersion = Long.MAX_VALUE;
Chunk minChunk = null;
for (Chunk c : chunks.values()) {
if (c.version < minVersion) {
minVersion = c.version;
minChunk = c;
}
}
retainChunk = minChunk;
} else {
lastCommittedVersion = version;
meta.remove("rollbackOnOpen");
retainChunk = null; retainChunk = null;
}
// the last chunk was not completely correct in the last store() // the last chunk was not completely correct in the last store()
// this needs to be updated now (it's better not to update right after // this needs to be updated now (it's better not to update right after
...@@ -983,10 +948,9 @@ public class MVStore { ...@@ -983,10 +948,9 @@ public class MVStore {
// some pages might have been changed in the meantime (in the newest version) // some pages might have been changed in the meantime (in the newest version)
unsavedPageCount = Math.max(0, unsavedPageCount - currentUnsavedPageCount); unsavedPageCount = Math.max(0, unsavedPageCount - currentUnsavedPageCount);
if (!temp) {
metaChanged = false; metaChanged = false;
lastStoredVersion = storeVersion; lastStoredVersion = storeVersion;
}
return version; return version;
} }
...@@ -1053,7 +1017,7 @@ public class MVStore { ...@@ -1053,7 +1017,7 @@ public class MVStore {
if (v > storeVersion) { if (v > storeVersion) {
continue; continue;
} }
Map<Integer, Chunk> freed = e.getValue(); HashMap<Integer, Chunk> freed = e.getValue();
for (Chunk f : freed.values()) { for (Chunk f : freed.values()) {
Chunk c = chunks.get(f.id); Chunk c = chunks.get(f.id);
if (c == null) { if (c == null) {
...@@ -1146,6 +1110,9 @@ public class MVStore { ...@@ -1146,6 +1110,9 @@ public class MVStore {
* @return if there are any changes * @return if there are any changes
*/ */
public boolean hasUnsavedChanges() { public boolean hasUnsavedChanges() {
;
// TODO maybe private; rename to hasUncommittedChanges
checkOpen(); checkOpen();
if (metaChanged) { if (metaChanged) {
return true; return true;
...@@ -1281,7 +1248,7 @@ public class MVStore { ...@@ -1281,7 +1248,7 @@ public class MVStore {
} }
/** /**
* Force all changes to be written to the storage. The default * Force all stored changes to be written to the storage. The default
* implementation calls FileChannel.force(true). * implementation calls FileChannel.force(true).
*/ */
public void sync() { public void sync() {
...@@ -1484,12 +1451,14 @@ public class MVStore { ...@@ -1484,12 +1451,14 @@ public class MVStore {
Chunk c = getChunk(pos); Chunk c = getChunk(pos);
long version = currentVersion; long version = currentVersion;
if (map == meta && currentStoreVersion >= 0) { if (map == meta && currentStoreVersion >= 0) {
if (Thread.currentThread() == currentStoreThread) {
// if the meta map is modified while storing, // if the meta map is modified while storing,
// then this freed page needs to be registered // then this freed page needs to be registered
// with the stored chunk, so that the old chunk // with the stored chunk, so that the old chunk
// can be re-used // can be re-used
version = currentStoreVersion; version = currentStoreVersion;
} }
}
registerFreePage(version, c.id, DataUtils.getPageMaxLength(pos), 1); registerFreePage(version, c.id, DataUtils.getPageMaxLength(pos), 1);
} }
...@@ -1663,9 +1632,8 @@ public class MVStore { ...@@ -1663,9 +1632,8 @@ public class MVStore {
} }
/** /**
* Get the maximum number of unsaved pages. If this number is exceeded, * Get the maximum number of unsaved pages. If this number is exceeded, the
* the unsaved changes are stored to disk, including uncommitted changes. * unsaved changes are stored to disk.
* Saved uncommitted changes are rolled back when opening the store.
* *
* @return the number of maximum unsaved pages * @return the number of maximum unsaved pages
*/ */
...@@ -1677,8 +1645,8 @@ public class MVStore { ...@@ -1677,8 +1645,8 @@ public class MVStore {
* Increment the number of unsaved pages. * Increment the number of unsaved pages.
*/ */
void registerUnsavedPage() { void registerUnsavedPage() {
unsavedPageCount++; int count = ++unsavedPageCount;
if (unsavedPageCount > unsavedPageCountMax && unsavedPageCountMax > 0) { if (count > unsavedPageCountMax && unsavedPageCountMax > 0) {
storeNeeded = true; storeNeeded = true;
} }
} }
...@@ -1689,7 +1657,7 @@ public class MVStore { ...@@ -1689,7 +1657,7 @@ public class MVStore {
void beforeWrite() { void beforeWrite() {
if (storeNeeded) { if (storeNeeded) {
storeNeeded = false; storeNeeded = false;
store(true); store();
} }
} }
...@@ -1711,12 +1679,22 @@ public class MVStore { ...@@ -1711,12 +1679,22 @@ public class MVStore {
* *
* @param version the new store version * @param version the new store version
*/ */
public void setStoreVersion(int version) { public synchronized void setStoreVersion(int version) {
checkOpen(); checkOpen();
markMetaChanged(); markMetaChanged();
meta.put("setting.storeVersion", Integer.toString(version)); meta.put("setting.storeVersion", Integer.toString(version));
} }
/**
* Revert to the beginning of the current version.
*/
public void rollback() {
;
// TODO document and test
rollbackTo(currentVersion);
}
/** /**
* Revert to the beginning of the given version. All later changes (stored * Revert to the beginning of the given version. All later changes (stored
* or not) are forgotten. All maps that were created later are closed. A * or not) are forgotten. All maps that were created later are closed. A
...@@ -1741,7 +1719,6 @@ public class MVStore { ...@@ -1741,7 +1719,6 @@ public class MVStore {
freedPageSpace.clear(); freedPageSpace.clear();
currentVersion = version; currentVersion = version;
setWriteVersion(version); setWriteVersion(version);
lastCommittedVersion = version;
metaChanged = false; metaChanged = false;
return; return;
} }
...@@ -1816,12 +1793,10 @@ public class MVStore { ...@@ -1816,12 +1793,10 @@ public class MVStore {
// rollback might have rolled back the stored chunk metadata as well // rollback might have rolled back the stored chunk metadata as well
Chunk c = chunks.get(lastChunkId - 1); Chunk c = chunks.get(lastChunkId - 1);
if (c != null) { if (c != null) {
markMetaChanged();
meta.put("chunk." + c.id, c.asString()); meta.put("chunk." + c.id, c.asString());
} }
currentVersion = version; currentVersion = version;
setWriteVersion(version); setWriteVersion(version);
lastCommittedVersion = version;
} }
private void revertTemp(long storeVersion) { private void revertTemp(long storeVersion) {
...@@ -1847,15 +1822,6 @@ public class MVStore { ...@@ -1847,15 +1822,6 @@ public class MVStore {
return currentVersion; return currentVersion;
} }
/**
* Get the last committed version.
*
* @return the version
*/
public long getCommittedVersion() {
return lastCommittedVersion;
}
/** /**
* Get the file store. * Get the file store.
* *
...@@ -1889,7 +1855,7 @@ public class MVStore { ...@@ -1889,7 +1855,7 @@ public class MVStore {
* @param map the map * @param map the map
* @param newName the new name * @param newName the new name
*/ */
public void renameMap(MVMap<?, ?> map, String newName) { public synchronized void renameMap(MVMap<?, ?> map, String newName) {
checkOpen(); checkOpen();
DataUtils.checkArgument(map != meta, DataUtils.checkArgument(map != meta,
"Renaming the meta map is not allowed"); "Renaming the meta map is not allowed");
...@@ -1912,7 +1878,7 @@ public class MVStore { ...@@ -1912,7 +1878,7 @@ public class MVStore {
* *
* @param map the map * @param map the map
*/ */
public void removeMap(MVMap<?, ?> map) { public synchronized void removeMap(MVMap<?, ?> map) {
checkOpen(); checkOpen();
DataUtils.checkArgument(map != meta, DataUtils.checkArgument(map != meta,
"Removing the meta map is not allowed"); "Removing the meta map is not allowed");
...@@ -1932,7 +1898,7 @@ public class MVStore { ...@@ -1932,7 +1898,7 @@ public class MVStore {
* @param id the map id * @param id the map id
* @return the name * @return the name
*/ */
String getMapName(int id) { synchronized String getMapName(int id) {
String m = meta.get("map." + id); String m = meta.get("map." + id);
return DataUtils.parseMap(m).get("name"); return DataUtils.parseMap(m).get("name");
} }
...@@ -1941,14 +1907,13 @@ public class MVStore { ...@@ -1941,14 +1907,13 @@ public class MVStore {
* Store all unsaved changes, if there are any that are committed. * Store all unsaved changes, if there are any that are committed.
*/ */
void storeInBackground() { void storeInBackground() {
if (closed || unsavedPageCount == 0) { if (unsavedPageCount == 0 || closed) {
return; return;
} }
// could also store when there are many unsaved pages, // could also store when there are many unsaved pages,
// but according to a test it doesn't really help // but according to a test it doesn't really help
if (lastStoredVersion >= lastCommittedVersion) {
return;
}
long time = getTime(); long time = getTime();
if (time <= lastStoreTime + writeDelay) { if (time <= lastStoreTime + writeDelay) {
return; return;
...@@ -1957,7 +1922,7 @@ public class MVStore { ...@@ -1957,7 +1922,7 @@ public class MVStore {
return; return;
} }
try { try {
store(true); store();
} catch (Exception e) { } catch (Exception e) {
if (backgroundExceptionHandler != null) { if (backgroundExceptionHandler != null) {
backgroundExceptionHandler.uncaughtException(null, e); backgroundExceptionHandler.uncaughtException(null, e);
...@@ -1981,13 +1946,13 @@ public class MVStore { ...@@ -1981,13 +1946,13 @@ public class MVStore {
} }
private void stopBackgroundThread() { private void stopBackgroundThread() {
if (backgroundThread == null) { Thread t = backgroundThread;
if (t == null) {
return; return;
} }
Thread t = backgroundThread;
backgroundThread = null; backgroundThread = null;
synchronized (this) { synchronized (backgroundThreadSync) {
notify(); backgroundThreadSync.notifyAll();
} }
try { try {
t.join(); t.join();
...@@ -1997,13 +1962,12 @@ public class MVStore { ...@@ -1997,13 +1962,12 @@ public class MVStore {
} }
/** /**
* Set the maximum delay in milliseconds to store committed changes (for * Set the maximum delay in milliseconds to commit changes.
* file-based stores).
* <p> * <p>
* The default is 1000, meaning committed changes are stored after at * The default is 1000, meaning changes are committed after at
* most one second. * most one second.
* <p> * <p>
* When the value is set to -1, committed changes are only written when * When the value is set to -1, changes are only written when
* calling the store method. When the value is set to 0, committed * calling the store method. When the value is set to 0, committed
* changes are immediately written on a commit, but please note this * changes are immediately written on a commit, but please note this
* decreases performance and does still not guarantee the disk will * decreases performance and does still not guarantee the disk will
...@@ -2012,6 +1976,8 @@ public class MVStore { ...@@ -2012,6 +1976,8 @@ public class MVStore {
* @param millis the maximum delay * @param millis the maximum delay
*/ */
public void setWriteDelay(int millis) { public void setWriteDelay(int millis) {
;
// TODO rename to commitDelay
if (writeDelay == millis) { if (writeDelay == millis) {
return; return;
} }
...@@ -2050,12 +2016,17 @@ public class MVStore { ...@@ -2050,12 +2016,17 @@ public class MVStore {
@Override @Override
public void run() { public void run() {
while (store.backgroundThread != null) { while (true) {
synchronized (store) { Thread t = store.backgroundThread;
if (t == null) {
break;
}
Object sync = store.backgroundThreadSync;
synchronized (sync) {
try { try {
store.wait(sleep); sync.wait(sleep);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// ignore continue;
} }
} }
store.storeInBackground(); store.storeInBackground();
...@@ -2145,9 +2116,8 @@ public class MVStore { ...@@ -2145,9 +2116,8 @@ public class MVStore {
/** /**
* Set the size of the write buffer, in MB (for file-based stores). * Set the size of the write buffer, in MB (for file-based stores).
* Changes are automatically stored if the buffer grows larger than * Unless auto-commit is disabled, changes are automatically stored if
* this. However, unless the changes are committed later on, they are * the buffer grows larger than this.
* rolled back when opening the store.
* <p> * <p>
* The default is 4 MB. * The default is 4 MB.
* <p> * <p>
......
...@@ -121,8 +121,7 @@ public class OffHeapStore extends FileStore { ...@@ -121,8 +121,7 @@ public class OffHeapStore extends FileStore {
@Override @Override
public void close() { public void close() {
truncate(0); // do nothing (keep the data until it is garbage collected)
freeSpace.clear();
} }
@Override @Override
......
...@@ -260,7 +260,7 @@ public class MVTableEngine implements TableEngine { ...@@ -260,7 +260,7 @@ public class MVTableEngine implements TableEngine {
public void compactFile(long maxCompactTime) { public void compactFile(long maxCompactTime) {
store.setRetentionTime(0); store.setRetentionTime(0);
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
while (store.compact(90)) { while (store.compact(99)) {
store.sync(); store.sync();
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
if (time > maxCompactTime) { if (time > maxCompactTime) {
...@@ -280,7 +280,7 @@ public class MVTableEngine implements TableEngine { ...@@ -280,7 +280,7 @@ public class MVTableEngine implements TableEngine {
public void close(long maxCompactTime) { public void close(long maxCompactTime) {
if (!store.isClosed() && store.getFileStore() != null) { if (!store.isClosed() && store.getFileStore() != null) {
if (!store.getFileStore().isReadOnly()) { if (!store.getFileStore().isReadOnly()) {
store.store(); transactionStore.close();
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
while (store.compact(90)) { while (store.compact(90)) {
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
......
...@@ -170,8 +170,10 @@ public class TransactionStore { ...@@ -170,8 +170,10 @@ public class TransactionStore {
public synchronized void close() { public synchronized void close() {
// to avoid losing transaction ids // to avoid losing transaction ids
settings.put(LAST_TRANSACTION_ID, "" + lastTransactionId); settings.put(LAST_TRANSACTION_ID, "" + lastTransactionId);
if (store.getFileStore() != null) {
store.store(); store.store();
} }
}
/** /**
* Begin a new transaction. * Begin a new transaction.
...@@ -191,7 +193,6 @@ public class TransactionStore { ...@@ -191,7 +193,6 @@ public class TransactionStore {
private void commitIfNeeded() { private void commitIfNeeded() {
if (store.getUnsavedPageCount() > MAX_UNSAVED_PAGES) { if (store.getUnsavedPageCount() > MAX_UNSAVED_PAGES) {
if (store.getFileStore() != null) { if (store.getFileStore() != null) {
store.commit();
store.store(); store.store();
} }
} }
...@@ -354,10 +355,10 @@ public class TransactionStore { ...@@ -354,10 +355,10 @@ public class TransactionStore {
firstOpenTransaction = -1; firstOpenTransaction = -1;
} }
if (store.getWriteDelay() == 0) { if (store.getWriteDelay() == 0) {
if (store.getFileStore() == null) { if (store.getFileStore() != null) {
return; store.store();
} }
store.commit(); return;
} }
// to avoid having to store the transaction log, // to avoid having to store the transaction log,
// if there is no open transaction, // if there is no open transaction,
...@@ -1207,7 +1208,7 @@ public class TransactionStore { ...@@ -1207,7 +1208,7 @@ public class TransactionStore {
*/ */
public Iterator<K> keyIterator(K from, boolean includeUncommitted) { public Iterator<K> keyIterator(K from, boolean includeUncommitted) {
Cursor<K> it = map.keyIterator(from); Cursor<K> it = map.keyIterator(from);
return wrapIterator(it, false); return wrapIterator(it, includeUncommitted);
} }
/** /**
......
...@@ -470,18 +470,18 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -470,18 +470,18 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
prof.interval = 1; prof.interval = 1;
prof.startCollecting(); prof.startCollecting();
if (test.mvStore) { if (test.mvStore) {
TestPerformance.main("-init", "-db", "9", "-size", "10000"); TestPerformance.main("-init", "-db", "9", "-size", "1000");
} else { } else {
TestPerformance.main("-init", "-db", "1"); TestPerformance.main("-init", "-db", "1");
} }
prof.stopCollecting(); prof.stopCollecting();
System.out.println(prof.getTop(3)); System.out.println(prof.getTop(30));
if (test.mvStore) { if (test.mvStore) {
prof = new Profiler(); prof = new Profiler();
prof.depth = 16; prof.depth = 16;
prof.interval = 1; prof.interval = 1;
prof.startCollecting(); prof.startCollecting();
TestPerformance.main("-init", "-db", "1", "-size", "10000"); TestPerformance.main("-init", "-db", "1", "-size", "1000");
prof.stopCollecting(); prof.stopCollecting();
System.out.println(prof.getTop(3)); System.out.println(prof.getTop(3));
} }
......
...@@ -43,7 +43,6 @@ public class TestConcurrent extends TestMVStore { ...@@ -43,7 +43,6 @@ public class TestConcurrent extends TestMVStore {
@Override @Override
public void test() throws Exception { public void test() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true); FileUtils.deleteRecursive(getBaseDir(), true);
FileUtils.createDirectories(getBaseDir()); FileUtils.createDirectories(getBaseDir());
FileUtils.deleteRecursive("memFS:", false); FileUtils.deleteRecursive("memFS:", false);
...@@ -110,7 +109,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -110,7 +109,7 @@ public class TestConcurrent extends TestMVStore {
m.clear(); m.clear();
s.removeMap(m); s.removeMap(m);
if (x % 5 == 0) { if (x % 5 == 0) {
s.incrementVersion(); s.commit();
} }
} }
task.get(); task.get();
...@@ -124,9 +123,9 @@ public class TestConcurrent extends TestMVStore { ...@@ -124,9 +123,9 @@ public class TestConcurrent extends TestMVStore {
} }
} }
assertEquals(1, chunkCount); assertEquals(1, chunkCount);
s.close(); s.close();
} }
FileUtils.deleteRecursive("memFS:", false);
} }
private void testConcurrentStoreAndRemoveMap() throws InterruptedException { private void testConcurrentStoreAndRemoveMap() throws InterruptedException {
...@@ -159,6 +158,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -159,6 +158,7 @@ public class TestConcurrent extends TestMVStore {
} }
task.get(); task.get();
s.close(); s.close();
FileUtils.deleteRecursive("memFS:", false);
} }
private void testConcurrentStoreAndClose() throws InterruptedException { private void testConcurrentStoreAndClose() throws InterruptedException {
...@@ -200,6 +200,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -200,6 +200,7 @@ public class TestConcurrent extends TestMVStore {
} }
s.close(); s.close();
} }
FileUtils.deleteRecursive("memFS:", false);
} }
/** /**
...@@ -249,7 +250,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -249,7 +250,7 @@ public class TestConcurrent extends TestMVStore {
} }
m.get(rand.nextInt(size)); m.get(rand.nextInt(size));
} }
s.incrementVersion(); s.commit();
Thread.sleep(1); Thread.sleep(1);
} }
task.get(); task.get();
...@@ -341,7 +342,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -341,7 +342,7 @@ public class TestConcurrent extends TestMVStore {
for (int k = 0; k < 10000; k++) { for (int k = 0; k < 10000; k++) {
Iterator<Integer> it = map.keyIterator(r.nextInt(len)); Iterator<Integer> it = map.keyIterator(r.nextInt(len));
long old = s.getCurrentVersion(); long old = s.getCurrentVersion();
s.incrementVersion(); s.commit();
s.setRetainVersion(old - 100); s.setRetainVersion(old - 100);
while (map.getVersion() == old) { while (map.getVersion() == old) {
Thread.yield(); Thread.yield();
...@@ -423,7 +424,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -423,7 +424,7 @@ public class TestConcurrent extends TestMVStore {
notDetected.incrementAndGet(); notDetected.incrementAndGet();
} }
} }
s.incrementVersion(); s.commit();
Thread.sleep(1); Thread.sleep(1);
} }
task.get(); task.get();
...@@ -438,7 +439,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -438,7 +439,7 @@ public class TestConcurrent extends TestMVStore {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
m.put(i, x); m.put(i, x);
} }
s.incrementVersion(); s.commit();
Task task = new Task() { Task task = new Task() {
@Override @Override
public void call() throws Exception { public void call() throws Exception {
...@@ -462,7 +463,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -462,7 +463,7 @@ public class TestConcurrent extends TestMVStore {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
m.put(i, x); m.put(i, x);
} }
s.incrementVersion(); s.commit();
Thread.sleep(1); Thread.sleep(1);
} }
task.get(); task.get();
......
...@@ -148,7 +148,7 @@ public class TestMVStore extends TestBase { ...@@ -148,7 +148,7 @@ public class TestMVStore extends TestBase {
s.store(); s.store();
} }
assertTrue(1000 < offHeap.getWriteCount()); assertTrue(1000 < offHeap.getWriteCount());
// s.close(); s.close();
s = new MVStore.Builder(). s = new MVStore.Builder().
fileStore(offHeap). fileStore(offHeap).
...@@ -253,7 +253,6 @@ public class TestMVStore extends TestBase { ...@@ -253,7 +253,6 @@ public class TestMVStore extends TestBase {
m = s.openMap("data"); m = s.openMap("data");
s.getFileStore().getFile().close(); s.getFileStore().getFile().close();
m.put(1, "Hello"); m.put(1, "Hello");
s.commit();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
if (exRef.get() != null) { if (exRef.get() != null) {
break; break;
...@@ -330,19 +329,19 @@ public class TestMVStore extends TestBase { ...@@ -330,19 +329,19 @@ public class TestMVStore extends TestBase {
fileName(fileName). fileName(fileName).
open(); open();
m = s.openMap("data"); m = s.openMap("data");
assertFalse(m.containsKey(1)); assertTrue(m.containsKey(1));
m.put(1, data); m.put(-1, data);
s.commit(); s.store();
m.put(2, data); m.put(-2, data);
s.close(); s.close();
s = new MVStore.Builder(). s = new MVStore.Builder().
fileName(fileName). fileName(fileName).
open(); open();
m = s.openMap("data"); m = s.openMap("data");
assertTrue(m.containsKey(1)); assertTrue(m.containsKey(-1));
assertFalse(m.containsKey(2)); assertTrue(m.containsKey(-2));
s.close(); s.close();
FileUtils.delete(fileName); FileUtils.delete(fileName);
...@@ -377,7 +376,7 @@ public class TestMVStore extends TestBase { ...@@ -377,7 +376,7 @@ public class TestMVStore extends TestBase {
m.put(1, "Hello"); m.put(1, "Hello");
s.store(); s.store();
long v = s.getCurrentVersion(); long v = s.getCurrentVersion();
m.put(2, "World"); m.put(2, "World.");
Thread.sleep(5); Thread.sleep(5);
// must not store, as nothing has been committed yet // must not store, as nothing has been committed yet
s.closeImmediately(); s.closeImmediately();
...@@ -386,9 +385,11 @@ public class TestMVStore extends TestBase { ...@@ -386,9 +385,11 @@ public class TestMVStore extends TestBase {
open(); open();
s.setWriteDelay(1); s.setWriteDelay(1);
m = s.openMap("data"); m = s.openMap("data");
assertEquals(null, m.get(2)); assertEquals("World.", m.get(2));
m.put(2, "World"); m.put(2, "World");
s.commit(); s.commit();
s.store();
v = s.getCurrentVersion();
m.put(3, "!"); m.put(3, "!");
for (int i = 100; i > 0; i--) { for (int i = 100; i > 0; i--) {
...@@ -400,7 +401,7 @@ public class TestMVStore extends TestBase { ...@@ -400,7 +401,7 @@ public class TestMVStore extends TestBase {
} }
Thread.sleep(1); Thread.sleep(1);
} }
s.close(); s.closeImmediately();
s = new MVStore.Builder(). s = new MVStore.Builder().
fileName(fileName). fileName(fileName).
...@@ -408,7 +409,7 @@ public class TestMVStore extends TestBase { ...@@ -408,7 +409,7 @@ public class TestMVStore extends TestBase {
m = s.openMap("data"); m = s.openMap("data");
assertEquals("Hello", m.get(1)); assertEquals("Hello", m.get(1));
assertEquals("World", m.get(2)); assertEquals("World", m.get(2));
assertFalse(m.containsKey(3)); assertEquals("!", m.get(3));
s.close(); s.close();
FileUtils.delete(fileName); FileUtils.delete(fileName);
...@@ -517,7 +518,7 @@ public class TestMVStore extends TestBase { ...@@ -517,7 +518,7 @@ public class TestMVStore extends TestBase {
MVMap<Integer, Integer> map; MVMap<Integer, Integer> map;
map = s.openMap("hello"); map = s.openMap("hello");
map.put(1, 10); map.put(1, 10);
long old = s.incrementVersion(); long old = s.commit();
s.renameMap(map, "world"); s.renameMap(map, "world");
map.put(2, 20); map.put(2, 20);
assertEquals("world", map.getName()); assertEquals("world", map.getName());
...@@ -557,7 +558,7 @@ public class TestMVStore extends TestBase { ...@@ -557,7 +558,7 @@ public class TestMVStore extends TestBase {
s.store(); s.store();
s.close(); s.close();
int[] expectedReadsForCacheSize = { int[] expectedReadsForCacheSize = {
3405, 2590, 1924, 1440, 1103, 956, 918 3405, 2590, 1924, 1440, 1108, 956, 918
}; };
for (int cacheSize = 0; cacheSize <= 6; cacheSize += 4) { for (int cacheSize = 0; cacheSize <= 6; cacheSize += 4) {
s = new MVStore.Builder(). s = new MVStore.Builder().
...@@ -816,8 +817,10 @@ public class TestMVStore extends TestBase { ...@@ -816,8 +817,10 @@ public class TestMVStore extends TestBase {
MVStore s = MVStore.open(fileName); MVStore s = MVStore.open(fileName);
assertEquals(0, s.getCurrentVersion()); assertEquals(0, s.getCurrentVersion());
assertEquals(0, s.getStoreVersion()); assertEquals(0, s.getStoreVersion());
s.setStoreVersion(0);
s.store();
s.setStoreVersion(1); s.setStoreVersion(1);
s.close(); s.closeImmediately();
s = MVStore.open(fileName); s = MVStore.open(fileName);
assertEquals(1, s.getCurrentVersion()); assertEquals(1, s.getCurrentVersion());
assertEquals(0, s.getStoreVersion()); assertEquals(0, s.getStoreVersion());
...@@ -840,7 +843,7 @@ public class TestMVStore extends TestBase { ...@@ -840,7 +843,7 @@ public class TestMVStore extends TestBase {
map.put(i, 10 * i); map.put(i, 10 * i);
} }
Iterator<Integer> it = map.keySet().iterator(); Iterator<Integer> it = map.keySet().iterator();
s.incrementVersion(); s.commit();
for (int i = 0; i < len; i += 2) { for (int i = 0; i < len; i += 2) {
map.remove(i); map.remove(i);
} }
...@@ -916,7 +919,7 @@ public class TestMVStore extends TestBase { ...@@ -916,7 +919,7 @@ public class TestMVStore extends TestBase {
long oldVersion = s.getCurrentVersion(); long oldVersion = s.getCurrentVersion();
// from now on, the old version is read-only // from now on, the old version is read-only
s.incrementVersion(); s.commit();
// more changes, in the new version // more changes, in the new version
// changes can be rolled back if required // changes can be rolled back if required
...@@ -976,7 +979,7 @@ public class TestMVStore extends TestBase { ...@@ -976,7 +979,7 @@ public class TestMVStore extends TestBase {
if (op == 1) { if (op == 1) {
m.put("1", "" + s.getCurrentVersion()); m.put("1", "" + s.getCurrentVersion());
} }
s.incrementVersion(); s.commit();
} }
for (int j = 0; j < s.getCurrentVersion(); j++) { for (int j = 0; j < s.getCurrentVersion(); j++) {
MVMap<String, String> old = m.openVersion(j); MVMap<String, String> old = m.openVersion(j);
...@@ -997,14 +1000,14 @@ public class TestMVStore extends TestBase { ...@@ -997,14 +1000,14 @@ public class TestMVStore extends TestBase {
MVMap<String, String> m; MVMap<String, String> m;
m = s.openMap("data"); m = s.openMap("data");
long first = s.getCurrentVersion(); long first = s.getCurrentVersion();
s.incrementVersion(); s.commit();
m.put("1", "Hello"); m.put("1", "Hello");
m.put("2", "World"); m.put("2", "World");
for (int i = 10; i < 20; i++) { for (int i = 10; i < 20; i++) {
m.put("" + i, "data"); m.put("" + i, "data");
} }
long old = s.getCurrentVersion(); long old = s.getCurrentVersion();
s.incrementVersion(); s.commit();
m.put("1", "Hallo"); m.put("1", "Hallo");
m.put("2", "Welt"); m.put("2", "Welt");
MVMap<String, String> mFirst; MVMap<String, String> mFirst;
...@@ -1119,7 +1122,7 @@ public class TestMVStore extends TestBase { ...@@ -1119,7 +1122,7 @@ public class TestMVStore extends TestBase {
assertTrue(s.hasUnsavedChanges()); assertTrue(s.hasUnsavedChanges());
MVMap<String, String> m0 = s.openMap("data0"); MVMap<String, String> m0 = s.openMap("data0");
m.put("1", "Hello"); m.put("1", "Hello");
assertEquals(1, s.incrementVersion()); assertEquals(1, s.commit());
s.rollbackTo(1); s.rollbackTo(1);
assertEquals(1, s.getCurrentVersion()); assertEquals(1, s.getCurrentVersion());
assertEquals("Hello", m.get("1")); assertEquals("Hello", m.get("1"));
...@@ -1167,16 +1170,13 @@ public class TestMVStore extends TestBase { ...@@ -1167,16 +1170,13 @@ public class TestMVStore extends TestBase {
assertEquals("Hello", m.get("1")); assertEquals("Hello", m.get("1"));
assertFalse(m0.isReadOnly()); assertFalse(m0.isReadOnly());
m.put("1", "Hallo"); m.put("1", "Hallo");
s.incrementVersion(); s.commit();
long v3 = s.getCurrentVersion(); long v3 = s.getCurrentVersion();
assertEquals(3, v3); assertEquals(3, v3);
long v4 = s.store();
assertEquals(4, v4);
assertEquals(4, s.getCurrentVersion());
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
assertEquals(4, s.getCurrentVersion()); assertEquals(3, s.getCurrentVersion());
m = s.openMap("data"); m = s.openMap("data");
m.put("1", "Hi"); m.put("1", "Hi");
s.store(); s.store();
...@@ -1185,7 +1185,7 @@ public class TestMVStore extends TestBase { ...@@ -1185,7 +1185,7 @@ public class TestMVStore extends TestBase {
s = openStore(fileName); s = openStore(fileName);
m = s.openMap("data"); m = s.openMap("data");
assertEquals("Hi", m.get("1")); assertEquals("Hi", m.get("1"));
s.rollbackTo(v4); s.rollbackTo(v3);
assertEquals("Hallo", m.get("1")); assertEquals("Hallo", m.get("1"));
s.close(); s.close();
...@@ -1212,7 +1212,7 @@ public class TestMVStore extends TestBase { ...@@ -1212,7 +1212,7 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
m2.put("" + i, "Test"); m2.put("" + i, "Test");
} }
long v1 = s.incrementVersion(); long v1 = s.commit();
assertEquals(1, v1); assertEquals(1, v1);
assertEquals(1, s.getCurrentVersion()); assertEquals(1, s.getCurrentVersion());
MVMap<String, String> m1 = s.openMap("data1"); MVMap<String, String> m1 = s.openMap("data1");
......
...@@ -73,7 +73,7 @@ public class TestMVStoreBenchmark extends TestBase { ...@@ -73,7 +73,7 @@ public class TestMVStoreBenchmark extends TestBase {
} }
private static long[] getMemoryUsed(int count, int size) { private long[] getMemoryUsed(int count, int size) {
long hash, tree, mv; long hash, tree, mv;
ArrayList<Map<Integer, String>> mapList; ArrayList<Map<Integer, String>> mapList;
long mem; long mem;
...@@ -107,6 +107,10 @@ public class TestMVStoreBenchmark extends TestBase { ...@@ -107,6 +107,10 @@ public class TestMVStoreBenchmark extends TestBase {
mv = getMemory() - mem; mv = getMemory() - mem;
mapList.size(); mapList.size();
trace("hash: " + hash / 1024 / 1024 + " mb");
trace("tree: " + tree / 1024 / 1024 + " mb");
trace("mv: " + mv / 1024 / 1024 + " mb");
return new long[]{hash, tree, mv}; return new long[]{hash, tree, mv};
} }
...@@ -179,7 +183,7 @@ public class TestMVStoreBenchmark extends TestBase { ...@@ -179,7 +183,7 @@ public class TestMVStoreBenchmark extends TestBase {
} }
time = System.currentTimeMillis() - time; time = System.currentTimeMillis() - time;
} }
// System.out.println(map.getClass().getName() + ": " + time); trace(map.getClass().getName() + ": " + time);
return time; return time;
} }
......
...@@ -141,7 +141,7 @@ public class TestMVTableEngine extends TestBase { ...@@ -141,7 +141,7 @@ public class TestMVTableEngine extends TestBase {
conn.close(); conn.close();
long sizeNew = FileUtils.size(getBaseDir() + "/mvstore" long sizeNew = FileUtils.size(getBaseDir() + "/mvstore"
+ Constants.SUFFIX_MV_FILE); + Constants.SUFFIX_MV_FILE);
assertTrue(sizeNew < sizeOld); assertTrue("new: " + sizeNew + " old: " + sizeOld, sizeNew < sizeOld);
} }
private void testTwoPhaseCommit() throws Exception { private void testTwoPhaseCommit() throws Exception {
......
...@@ -59,6 +59,7 @@ public class TestTransactionStore extends TestBase { ...@@ -59,6 +59,7 @@ public class TestTransactionStore extends TestBase {
private void testStopWhileCommitting() throws Exception { private void testStopWhileCommitting() throws Exception {
String fileName = getBaseDir() + "/testStopWhileCommitting.h3"; String fileName = getBaseDir() + "/testStopWhileCommitting.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
Random r = new Random(0);
for (int i = 0; i < 10;) { for (int i = 0; i < 10;) {
MVStore s; MVStore s;
...@@ -100,6 +101,16 @@ public class TestTransactionStore extends TestBase { ...@@ -100,6 +101,16 @@ public class TestTransactionStore extends TestBase {
task.get(); task.get();
store.close(); store.close();
s = MVStore.open(fileName); s = MVStore.open(fileName);
// roll back a bit, until we have some undo log entries
assertTrue(s.hasMap("undoLog"));
for (int back = 0; back < 100; back++) {
int minus = r.nextInt(10);
s.rollbackTo(Math.max(0, s.getCurrentVersion() - minus));
MVMap<?, ?> undo = s.openMap("undoLog");
if (undo.size() > 0) {
break;
}
}
ts = new TransactionStore(s); ts = new TransactionStore(s);
List<Transaction> list = ts.getOpenTransactions(); List<Transaction> list = ts.getOpenTransactions();
if (list.size() != 0) { if (list.size() != 0) {
...@@ -111,10 +122,6 @@ public class TestTransactionStore extends TestBase { ...@@ -111,10 +122,6 @@ public class TestTransactionStore extends TestBase {
s.close(); s.close();
FileUtils.delete(fileName); FileUtils.delete(fileName);
assertFalse(FileUtils.exists(fileName)); assertFalse(FileUtils.exists(fileName));
FileUtils.delete(fileName);
assertFalse(FileUtils.exists(fileName));
s.close();
FileUtils.delete(fileName);
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论