Unverified 提交 422f2826 authored 作者: Andrei Tokar's avatar Andrei Tokar 提交者: GitHub

Merge pull request #1627 from h2database/append-single

Use lock to protect append buffer
...@@ -48,9 +48,11 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -48,9 +48,11 @@ public class MVMap<K, V> extends AbstractMap<K, V>
private final DataType valueType; private final DataType valueType;
private final int keysPerPage; private final int keysPerPage;
private final boolean singleWriter; private final boolean singleWriter;
private final K keysBuffer[]; private final K[] keysBuffer;
private final V valuesBuffer[]; private final V[] valuesBuffer;
private final Object lock = new Object();
private volatile boolean notificationRequested;
/** /**
* Whether the map is closed. Volatile so we don't accidentally write to a * Whether the map is closed. Volatile so we don't accidentally write to a
...@@ -619,16 +621,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -619,16 +621,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
return 0; return 0;
} }
assert p.getKeyCount() > 0; assert p.getKeyCount() > 0;
@SuppressWarnings("unchecked") return rewritePage(p) ? 0 : 1;
K key = (K) p.getKey(0);
V value = get(key);
if (value != null) {
if (isClosed()) {
return 0;
}
replace(key, value, value);
}
return 1;
} }
int writtenPageCount = 0; int writtenPageCount = 0;
for (int i = 0; i < getChildPageCount(p); i++) { for (int i = 0; i < getChildPageCount(p); i++) {
...@@ -658,14 +651,8 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -658,14 +651,8 @@ public class MVMap<K, V> extends AbstractMap<K, V>
while (!p2.isLeaf()) { while (!p2.isLeaf()) {
p2 = p2.getChildPage(0); p2 = p2.getChildPage(0);
} }
@SuppressWarnings("unchecked") if (rewritePage(p2)) {
K key = (K) p2.getKey(0); return 0;
V value = get(key);
if (value != null) {
if (isClosed()) {
return 0;
}
replace(key, value, value);
} }
writtenPageCount++; writtenPageCount++;
} }
...@@ -673,6 +660,19 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -673,6 +660,19 @@ public class MVMap<K, V> extends AbstractMap<K, V>
return writtenPageCount; return writtenPageCount;
} }
private boolean rewritePage(Page p) {
@SuppressWarnings("unchecked")
K key = (K) p.getKey(0);
V value = get(key);
if (value != null) {
if (isClosed()) {
return true;
}
replace(key, value, value);
}
return false;
}
/** /**
* Get a cursor to iterate over a number of keys and values. * Get a cursor to iterate over a number of keys and values.
* *
...@@ -779,17 +779,19 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -779,17 +779,19 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @return the root page * @return the root page
*/ */
public final Page getRootPage() { public final Page getRootPage() {
return getRoot().root; return flushAndGetRoot().root;
} }
public final RootReference getRoot() { public RootReference getRoot() {
RootReference rootReference = getRootInternal(); return root.get();
return singleWriter && rootReference.getAppendCounter() > 0 ?
flushAppendBuffer(rootReference) : rootReference;
} }
private RootReference getRootInternal() { public RootReference flushAndGetRoot() {
return root.get(); RootReference rootReference = getRoot();
if (singleWriter && rootReference.getAppendCounter() > 0) {
return flushAppendBuffer(rootReference, false);
}
return rootReference;
} }
final void setRoot(Page rootPage) { final void setRoot(Page rootPage) {
...@@ -814,13 +816,14 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -814,13 +816,14 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/ */
private RootReference setNewRoot(RootReference oldRoot, Page newRootPage, private RootReference setNewRoot(RootReference oldRoot, Page newRootPage,
int attemptUpdateCounter, boolean obeyLock) { int attemptUpdateCounter, boolean obeyLock) {
RootReference currentRoot = getRoot(); RootReference currentRoot = flushAndGetRoot();
assert newRootPage != null || currentRoot != null; assert newRootPage != null || currentRoot != null;
if (currentRoot != oldRoot && oldRoot != null) { if (currentRoot != oldRoot && oldRoot != null) {
return null; return null;
} }
RootReference previous = currentRoot; RootReference previous = currentRoot;
int appendCounter = 0;
long updateCounter = 1; long updateCounter = 1;
long newVersion = INITIAL_VERSION; long newVersion = INITIAL_VERSION;
if(currentRoot != null) { if(currentRoot != null) {
...@@ -834,12 +837,13 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -834,12 +837,13 @@ public class MVMap<K, V> extends AbstractMap<K, V>
newVersion = currentRoot.version; newVersion = currentRoot.version;
previous = currentRoot.previous; previous = currentRoot.previous;
appendCounter = currentRoot.getAppendCounter();
updateCounter += currentRoot.updateCounter; updateCounter += currentRoot.updateCounter;
attemptUpdateCounter += currentRoot.updateAttemptCounter; attemptUpdateCounter += currentRoot.updateAttemptCounter;
} }
RootReference updatedRootReference = new RootReference(newRootPage, newVersion, previous, updateCounter, RootReference updatedRootReference = new RootReference(newRootPage, newVersion, previous, appendCounter,
attemptUpdateCounter, false); updateCounter, attemptUpdateCounter);
boolean success = root.compareAndSet(currentRoot, updatedRootReference); boolean success = root.compareAndSet(currentRoot, updatedRootReference);
return success ? updatedRootReference : null; return success ? updatedRootReference : null;
} }
...@@ -858,7 +862,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -858,7 +862,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
void rollbackRoot(long version) void rollbackRoot(long version)
{ {
RootReference rootReference = getRoot(); RootReference rootReference = flushAndGetRoot();
RootReference previous; RootReference previous;
while (rootReference.version >= version && (previous = rootReference.previous) != null) { while (rootReference.version >= version && (previous = rootReference.previous) != null) {
if (root.compareAndSet(rootReference, previous)) { if (root.compareAndSet(rootReference, previous)) {
...@@ -1070,7 +1074,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1070,7 +1074,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
final boolean hasChangesSince(long version) { final boolean hasChangesSince(long version) {
RootReference rootReference = getRoot(); RootReference rootReference = getRoot();
Page root = rootReference.root; Page root = rootReference.root;
return !root.isSaved() && root.getTotalCount() > 0 || return !root.isSaved() && rootReference.getTotalCount() > 0 ||
getVersion(rootReference) > version; getVersion(rootReference) > version;
} }
...@@ -1123,7 +1127,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1123,7 +1127,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
final RootReference setWriteVersion(long writeVersion) { final RootReference setWriteVersion(long writeVersion) {
int attempt = 0; int attempt = 0;
while(true) { while(true) {
RootReference rootReference = getRoot(); RootReference rootReference = flushAndGetRoot();
if(rootReference.version >= writeVersion) { if(rootReference.version >= writeVersion) {
return rootReference; return rootReference;
} else if (isClosed()) { } else if (isClosed()) {
...@@ -1198,83 +1202,138 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1198,83 +1202,138 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* If map was used in append mode, this method will ensure that append buffer * If map was used in append mode, this method will ensure that append buffer
* is flushed - emptied with all entries inserted into map as a new leaf. * is flushed - emptied with all entries inserted into map as a new leaf.
* @param rootReference current RootReference * @param rootReference current RootReference
* @param lockedForUpdate whether rootReference is pre-locked already and
* should stay locked upon return
* @return potentially updated RootReference * @return potentially updated RootReference
*/ */
private RootReference flushAppendBuffer(RootReference rootReference) { private RootReference flushAppendBuffer(RootReference rootReference, boolean lockedForUpdate) {
int attempt = 0; IntValueHolder unsavedMemoryHolder = new IntValueHolder();
int keyCount; RootReference lockedRootReference = lockedForUpdate ? rootReference : null;
while((keyCount = rootReference.getAppendCounter()) > 0) { try {
Page page = Page.createLeaf(this, int attempt = 0;
Arrays.copyOf(keysBuffer, keyCount), int keyCount;
Arrays.copyOf(valuesBuffer, keyCount), while ((keyCount = rootReference.getAppendCounter()) > 0) {
0); if (lockedRootReference == null) {
CursorPos pos = rootReference.root.getAppendCursorPos(null); lockedRootReference = tryLock(rootReference, ++attempt);
assert page.map == this; rootReference = lockedRootReference == null ? getRoot() : lockedRootReference;
assert pos != null; continue;
assert page.getKeyCount() > 0;
Object key = page.getKey(0);
assert pos.index < 0 : pos.index;
int index = -pos.index - 1;
assert index == pos.page.getKeyCount() : index + " != " + pos.page.getKeyCount();
Page p = pos.page;
pos = pos.parent;
CursorPos tip = pos;
int unsavedMemory = page.getMemory();
while (true) {
if (pos == null) {
if (p.getKeyCount() == 0) {
p = page;
} else {
Object keys[] = new Object[] { key };
Page.PageReference children[] = new Page.PageReference[] {
new Page.PageReference(p),
new Page.PageReference(page)};
p = Page.createNode(this, keys, children, p.getTotalCount() + page.getTotalCount(), 0);
}
break;
} }
Page c = p;
p = pos.page; Page rootPage = rootReference.root;
index = pos.index;
CursorPos pos = rootPage.getAppendCursorPos(null);
assert pos != null;
assert pos.index < 0 : pos.index;
int index = -pos.index - 1;
assert index == pos.page.getKeyCount() : index + " != " + pos.page.getKeyCount();
Page p = pos.page;
CursorPos tip = pos;
pos = pos.parent; pos = pos.parent;
p = p.copy();
p.setChild(index, page); int remainingBuffer = 0;
p.insertNode(index, key, c); Page page = null;
if ((keyCount = p.getKeyCount()) <= store.getKeysPerPage() && int available = store.getKeysPerPage() - p.getKeyCount();
(p.getMemory() < store.getMaxPageSize() || keyCount <= (p.isLeaf() ? 1 : 2))) { if (available > 0) {
break; p = p.copy();
if (keyCount <= available) {
p.expand(keyCount, keysBuffer, valuesBuffer);
} else {
p.expand(available, keysBuffer, valuesBuffer);
keyCount -= available;
if (lockedForUpdate) {
System.arraycopy(keysBuffer, available, keysBuffer, 0, keyCount);
System.arraycopy(valuesBuffer, available, valuesBuffer, 0, keyCount);
remainingBuffer = keyCount;
} else {
Object[] keys = new Object[keyCount];
Object[] values = new Object[keyCount];
System.arraycopy(keysBuffer, available, keys, 0, keyCount);
System.arraycopy(valuesBuffer, available, values, 0, keyCount);
page = Page.createLeaf(this, keys, values, 0);
}
}
} else {
page = Page.createLeaf(this,
Arrays.copyOf(keysBuffer, keyCount),
Arrays.copyOf(valuesBuffer, keyCount),
0);
} }
int at = keyCount - 2;
key = p.getKey(at); unsavedMemoryHolder.value = 0;
page = p.split(at); if (page != null) {
unsavedMemory += p.getMemory() + page.getMemory(); assert page.map == this;
} assert page.getKeyCount() > 0;
unsavedMemory += p.getMemory(); Object key = page.getKey(0);
while (pos != null) { unsavedMemoryHolder.value += page.getMemory();
Page c = p; while (true) {
p = pos.page; if (pos == null) {
p = p.copy(); if (p.getKeyCount() == 0) {
p.setChild(pos.index, c); p = page;
unsavedMemory += p.getMemory(); } else {
pos = pos.parent; Object[] keys = new Object[]{key};
} Page.PageReference[] children = new Page.PageReference[]{
RootReference updatedRootReference = new RootReference(rootReference, p, ++attempt); new Page.PageReference(p),
if(root.compareAndSet(rootReference, updatedRootReference)) { new Page.PageReference(page)};
while (tip != null) { p = Page.createNode(this, keys, children, p.getTotalCount() + page.getTotalCount(), 0);
tip.page.removePage(); }
tip = tip.parent; break;
}
Page c = p;
p = pos.page;
index = pos.index;
pos = pos.parent;
p = p.copy();
p.setChild(index, page);
p.insertNode(index, key, c);
if ((keyCount = p.getKeyCount()) <= store.getKeysPerPage() &&
(p.getMemory() < store.getMaxPageSize() || keyCount <= (p.isLeaf() ? 1 : 2))) {
break;
}
int at = keyCount - 2;
key = p.getKey(at);
page = p.split(at);
unsavedMemoryHolder.value += p.getMemory() + page.getMemory();
}
} }
if (store.getFileStore() != null) { p = replacePage(pos, p, unsavedMemoryHolder);
store.registerUnsavedPage(unsavedMemory);
RootReference updatedRootReference = new RootReference(rootReference, p, remainingBuffer, lockedForUpdate);
if (root.compareAndSet(rootReference, updatedRootReference)) {
lockedRootReference = null;
while (tip != null) {
tip.page.removePage();
tip = tip.parent;
}
if (store.getFileStore() != null) {
store.registerUnsavedPage(unsavedMemoryHolder.value);
}
assert lockedForUpdate || updatedRootReference.getAppendCounter() == 0;
return updatedRootReference;
} }
assert updatedRootReference.getAppendCounter() == 0; rootReference = getRoot();
return updatedRootReference; }
} finally {
if (lockedRootReference != null && !lockedForUpdate) {
assert rootReference.root == lockedRootReference.root;
rootReference = unlockRoot(lockedRootReference.root, lockedRootReference.appendCounter);
} }
rootReference = getRootInternal();
} }
return rootReference; return rootReference;
} }
private static Page replacePage(CursorPos path, Page replacement, IntValueHolder unsavedMemoryHolder) {
int unsavedMemory = replacement.getMemory();
while (path != null) {
Page child = replacement;
replacement = path.page.copy();
replacement.setChild(path.index, child);
unsavedMemory += replacement.getMemory();
path = path.parent;
}
unsavedMemoryHolder.value += unsavedMemory;
return replacement;
}
/** /**
* Appends entry to this map. this method is NOT thread safe and can not be used * Appends entry to this map. this method is NOT thread safe and can not be used
* neither concurrently, nor in combination with any method that updates this map. * neither concurrently, nor in combination with any method that updates this map.
...@@ -1284,22 +1343,19 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1284,22 +1343,19 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @param value to be appended * @param value to be appended
*/ */
public void append(K key, V value) { public void append(K key, V value) {
int attempt = 0; RootReference rootReference = lockRoot(getRoot(), 1);
boolean success = false; int appendCounter = rootReference.getAppendCounter();
while(!success) { try {
RootReference rootReference = getRootInternal();
int appendCounter = rootReference.getAppendCounter();
if (appendCounter >= keysPerPage) { if (appendCounter >= keysPerPage) {
beforeWrite(); rootReference = flushAppendBuffer(rootReference, true);
rootReference = flushAppendBuffer(rootReference);
appendCounter = rootReference.getAppendCounter(); appendCounter = rootReference.getAppendCounter();
assert appendCounter < keysPerPage; assert appendCounter < keysPerPage;
} }
keysBuffer[appendCounter] = key; keysBuffer[appendCounter] = key;
valuesBuffer[appendCounter] = value; valuesBuffer[appendCounter] = value;
++appendCounter;
RootReference updatedRootReference = new RootReference(rootReference, appendCounter + 1, ++attempt); } finally {
success = root.compareAndSet(rootReference, updatedRootReference); unlockRoot(rootReference.root, appendCounter);
} }
} }
...@@ -1309,24 +1365,25 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1309,24 +1365,25 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* Non-updating method may be used concurrently, but latest removal may not be visible. * Non-updating method may be used concurrently, but latest removal may not be visible.
*/ */
public void trimLast() { public void trimLast() {
int attempt = 0; RootReference rootReference = getRoot();
boolean success; int appendCounter = rootReference.getAppendCounter();
do { boolean useRegularRemove = appendCounter == 0;
RootReference rootReference = getRootInternal(); if (!useRegularRemove) {
int appendCounter = rootReference.getAppendCounter(); rootReference = lockRoot(rootReference, 1);
if (appendCounter > 0) { appendCounter = rootReference.getAppendCounter();
RootReference updatedRootReference = new RootReference(rootReference, appendCounter - 1, ++attempt); useRegularRemove = appendCounter == 0;
success = root.compareAndSet(rootReference, updatedRootReference); if (!useRegularRemove) {
} else { --appendCounter;
assert rootReference.root.getKeyCount() > 0;
Page lastLeaf = rootReference.root.getAppendCursorPos(null).page;
assert lastLeaf.isLeaf();
assert lastLeaf.getKeyCount() > 0;
Object key = lastLeaf.getKey(lastLeaf.getKeyCount() - 1);
success = remove(key) != null;
assert success;
} }
} while(!success); unlockRoot(rootReference.root, appendCounter);
}
if (useRegularRemove) {
Page lastLeaf = rootReference.root.getAppendCursorPos(null).page;
assert lastLeaf.isLeaf();
assert lastLeaf.getKeyCount() > 0;
Object key = lastLeaf.getKey(lastLeaf.getKeyCount() - 1);
remove(key);
}
} }
@Override @Override
...@@ -1365,38 +1422,36 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1365,38 +1422,36 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/ */
public final byte appendCounter; public final byte appendCounter;
RootReference(Page root, long version, RootReference previous, RootReference(Page root, long version, RootReference previous, int appendCounter, long updateCounter, long updateAttemptCounter) {
long updateCounter, long updateAttemptCounter,
boolean lockedForUpdate) {
this.root = root; this.root = root;
this.version = version; this.version = version;
this.previous = previous; this.previous = previous;
this.updateCounter = updateCounter; this.updateCounter = updateCounter;
this.updateAttemptCounter = updateAttemptCounter; this.updateAttemptCounter = updateAttemptCounter;
this.lockedForUpdate = lockedForUpdate; this.lockedForUpdate = false;
this.appendCounter = 0; this.appendCounter = (byte)appendCounter;
} }
// This one is used for locking // This one is used for locking
RootReference(RootReference r) { RootReference(RootReference r, int attempt) {
this.root = r.root; this.root = r.root;
this.version = r.version; this.version = r.version;
this.previous = r.previous; this.previous = r.previous;
this.updateCounter = r.updateCounter; this.updateCounter = r.updateCounter + 1;
this.updateAttemptCounter = r.updateAttemptCounter; this.updateAttemptCounter = r.updateAttemptCounter + attempt;
this.lockedForUpdate = true; this.lockedForUpdate = true;
this.appendCounter = 0; this.appendCounter = r.appendCounter;
} }
// This one is used for unlocking // This one is used for unlocking
RootReference(RootReference r, Page root, int attempt) { RootReference(RootReference r, Page root, int appendCounter, boolean lockedForUpdate) {
this.root = root; this.root = root;
this.version = r.version; this.version = r.version;
this.previous = r.previous; this.previous = r.previous;
this.updateCounter = r.updateCounter + 1; this.updateCounter = r.updateCounter;
this.updateAttemptCounter = r.updateAttemptCounter + attempt; this.updateAttemptCounter = r.updateAttemptCounter;
this.lockedForUpdate = false; this.lockedForUpdate = lockedForUpdate;
this.appendCounter = 0; this.appendCounter = (byte)appendCounter;
} }
// This one is used for version change // This one is used for version change
...@@ -1426,21 +1481,14 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1426,21 +1481,14 @@ public class MVMap<K, V> extends AbstractMap<K, V>
this.appendCounter = 0; this.appendCounter = 0;
} }
// This one is used for append buffer maintenance
RootReference(RootReference r, int appendCounter, int attempt) {
this.root = r.root;
this.version = r.version;
this.previous = r.previous;
this.updateCounter = r.updateCounter + 1;
this.updateAttemptCounter = r.updateAttemptCounter + attempt;
this.lockedForUpdate = r.lockedForUpdate;
this.appendCounter = (byte)appendCounter;
}
public int getAppendCounter() { public int getAppendCounter() {
return appendCounter & 0xff; return appendCounter & 0xff;
} }
public long getTotalCount() {
return root.getTotalCount() + getAppendCounter();
}
@Override @Override
public String toString() { public String toString() {
return "RootReference("+ System.identityHashCode(root)+","+version+","+ lockedForUpdate +")"; return "RootReference("+ System.identityHashCode(root)+","+version+","+ lockedForUpdate +")";
...@@ -1707,36 +1755,32 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1707,36 +1755,32 @@ public class MVMap<K, V> extends AbstractMap<K, V>
public void reset() {} public void reset() {}
} }
@SuppressWarnings("unchecked")
public V operate(K key, V value, DecisionMaker<? super V> decisionMaker) { public V operate(K key, V value, DecisionMaker<? super V> decisionMaker) {
beforeWrite(); beforeWrite();
IntValueHolder unsavedMemoryHolder = new IntValueHolder();
int attempt = 0; int attempt = 0;
RootReference oldRootReference = null;
while(true) { while(true) {
RootReference rootReference = getRoot(); RootReference rootReference = flushAndGetRoot();
int contention = 0; RootReference lockedRootReference = null;
if (oldRootReference != null) { if ((++attempt > 3 || rootReference.lockedForUpdate)) {
long updateAttemptCounter = rootReference.updateAttemptCounter - lockedRootReference = lockRoot(rootReference, attempt);
oldRootReference.updateAttemptCounter; rootReference = lockedRootReference;
assert updateAttemptCounter >= 0 : updateAttemptCounter;
long updateCounter = rootReference.updateCounter - oldRootReference.updateCounter;
assert updateCounter >= 0 : updateCounter;
assert updateAttemptCounter >= updateCounter : updateAttemptCounter + " >= " + updateCounter;
contention = (int)((updateAttemptCounter+1) / (updateCounter+1));
} }
oldRootReference = rootReference; Page rootPage = rootReference.root;
++attempt; int appendCounter = rootReference.getAppendCounter();
CursorPos pos = traverseDown(rootReference.root, key); CursorPos tip;
Page p = pos.page; V result;
int index = pos.index; unsavedMemoryHolder.value = 0;
CursorPos tip = pos;
pos = pos.parent;
@SuppressWarnings("unchecked")
V result = index < 0 ? null : (V)p.getValue(index);
Decision decision = decisionMaker.decide(result, value);
int unsavedMemory = 0;
boolean needUnlock = false;
try { try {
CursorPos pos = traverseDown(rootPage, key);
Page p = pos.page;
int index = pos.index;
tip = pos;
pos = pos.parent;
result = index < 0 ? null : (V)p.getValue(index);
Decision decision = decisionMaker.decide(result, value);
switch (decision) { switch (decision) {
case REPEAT: case REPEAT:
decisionMaker.reset(); decisionMaker.reset();
...@@ -1755,10 +1799,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1755,10 +1799,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
} }
return null; return null;
} }
if (attempt > 2 && !(needUnlock = lockRoot(decisionMaker, rootReference,
attempt, contention))) {
continue;
}
if (p.getTotalCount() == 1 && pos != null) { if (p.getTotalCount() == 1 && pos != null) {
p = pos.page; p = pos.page;
index = pos.index; index = pos.index;
...@@ -1775,10 +1816,6 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1775,10 +1816,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
break; break;
} }
case PUT: { case PUT: {
if (attempt > 2 && !(needUnlock = lockRoot(decisionMaker, rootReference,
attempt, contention))) {
continue;
}
value = decisionMaker.selectValue(result, value); value = decisionMaker.selectValue(result, value);
p = p.copy(); p = p.copy();
if (index < 0) { if (index < 0) {
...@@ -1791,11 +1828,10 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1791,11 +1828,10 @@ public class MVMap<K, V> extends AbstractMap<K, V>
int at = keyCount >> 1; int at = keyCount >> 1;
Object k = p.getKey(at); Object k = p.getKey(at);
Page split = p.split(at); Page split = p.split(at);
unsavedMemory += p.getMemory(); unsavedMemoryHolder.value += p.getMemory() + split.getMemory();
unsavedMemory += split.getMemory();
if (pos == null) { if (pos == null) {
Object keys[] = { k }; Object[] keys = { k };
Page.PageReference children[] = { Page.PageReference[] children = {
new Page.PageReference(p), new Page.PageReference(p),
new Page.PageReference(split) new Page.PageReference(split)
}; };
...@@ -1816,70 +1852,97 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1816,70 +1852,97 @@ public class MVMap<K, V> extends AbstractMap<K, V>
break; break;
} }
} }
unsavedMemory += p.getMemory(); p = replacePage(pos, p, unsavedMemoryHolder);
while (pos != null) { rootPage = p;
Page c = p; if(lockedRootReference == null && !updateRoot(rootReference, p, attempt)) {
p = pos.page;
p = p.copy();
p.setChild(pos.index, c);
unsavedMemory += p.getMemory();
pos = pos.parent;
}
if(needUnlock) {
unlockRoot(p, attempt);
needUnlock = false;
} else if(!updateRoot(rootReference, p, attempt)) {
decisionMaker.reset(); decisionMaker.reset();
continue; continue;
} }
while (tip != null) {
tip.page.removePage();
tip = tip.parent;
}
if (store.getFileStore() != null) {
store.registerUnsavedPage(unsavedMemory);
}
return result;
} finally { } finally {
if(needUnlock) { if(lockedRootReference != null) {
unlockRoot(rootReference.root, attempt); unlockRoot(rootPage, appendCounter);
} }
} }
while (tip != null) {
tip.page.removePage();
tip = tip.parent;
}
if (store.getFileStore() != null) {
store.registerUnsavedPage(unsavedMemoryHolder.value);
}
return result;
} }
} }
private boolean lockRoot(DecisionMaker<? super V> decisionMaker, RootReference rootReference, private RootReference lockRoot(RootReference rootReference, int attempt) {
int attempt, int contention) { while(true) {
boolean success = lockRoot(rootReference); RootReference lockedRootReference = tryLock(rootReference, attempt++);
if (!success) { if (lockedRootReference != null) {
decisionMaker.reset(); return lockedRootReference;
if(attempt > 4) { }
if (attempt <= 24) { rootReference = getRoot();
Thread.yield(); }
} else { }
private RootReference tryLock(RootReference rootReference, int attempt) {
if (!rootReference.lockedForUpdate) {
RootReference lockedRootReference = new RootReference(rootReference, attempt);
if (root.compareAndSet(rootReference, lockedRootReference)) {
return lockedRootReference;
}
}
RootReference oldRootReference = rootReference.previous;
int contention = 1;
if (oldRootReference != null) {
long updateAttemptCounter = rootReference.updateAttemptCounter -
oldRootReference.updateAttemptCounter;
assert updateAttemptCounter >= 0 : updateAttemptCounter;
long updateCounter = rootReference.updateCounter - oldRootReference.updateCounter;
assert updateCounter >= 0 : updateCounter;
assert updateAttemptCounter >= updateCounter : updateAttemptCounter + " >= " + updateCounter;
contention += (int)((updateAttemptCounter+1) / (updateCounter+1));
}
if(attempt > 4) {
if (attempt <= 12) {
Thread.yield();
} else if (attempt <= 24) {
try {
Thread.sleep(0, 10 * contention + 5);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
} else {
synchronized (lock) {
notificationRequested = true;
try { try {
Thread.sleep(0, 100 / contention + 50); lock.wait(100);
} catch (InterruptedException ex) { } catch (InterruptedException ignore) {
throw new RuntimeException(ex);
} }
} }
} }
} }
return success; return null;
}
private boolean lockRoot(RootReference rootReference) {
return !rootReference.lockedForUpdate
&& root.compareAndSet(rootReference, new RootReference(rootReference));
} }
private void unlockRoot(Page newRoot, int attempt) { private RootReference unlockRoot(Page newRoot, int appendCounter) {
RootReference updatedRootReference;
boolean success; boolean success;
do { do {
RootReference rootReference = getRoot(); RootReference rootReference = getRoot();
RootReference updatedRootReference = new RootReference(rootReference, newRoot, attempt); assert rootReference.lockedForUpdate;
updatedRootReference = new RootReference(rootReference, newRoot, appendCounter, false);
success = root.compareAndSet(rootReference, updatedRootReference); success = root.compareAndSet(rootReference, updatedRootReference);
} while(!success); } while(!success);
if (notificationRequested) {
synchronized (lock) {
notificationRequested = false;
lock.notifyAll();;
}
}
return updatedRootReference;
} }
private static CursorPos traverseDown(Page p, Object key) { private static CursorPos traverseDown(Page p, Object key) {
...@@ -1929,4 +1992,9 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1929,4 +1992,9 @@ public class MVMap<K, V> extends AbstractMap<K, V>
} }
} }
private static final class IntValueHolder {
int value;
IntValueHolder() {}
}
} }
...@@ -1401,7 +1401,7 @@ public class MVStore implements AutoCloseable { ...@@ -1401,7 +1401,7 @@ public class MVStore implements AutoCloseable {
try { try {
ChunkIdsCollector collector = new ChunkIdsCollector(meta.getId()); ChunkIdsCollector collector = new ChunkIdsCollector(meta.getId());
long oldestVersionToKeep = getOldestVersionToKeep(); long oldestVersionToKeep = getOldestVersionToKeep();
MVMap.RootReference rootReference = meta.getRoot(); MVMap.RootReference rootReference = meta.flushAndGetRoot();
if (fast) { if (fast) {
MVMap.RootReference previous; MVMap.RootReference previous;
while (rootReference.version >= oldestVersionToKeep && (previous = rootReference.previous) != null) { while (rootReference.version >= oldestVersionToKeep && (previous = rootReference.previous) != null) {
......
...@@ -493,6 +493,16 @@ public abstract class Page implements Cloneable ...@@ -493,6 +493,16 @@ public abstract class Page implements Cloneable
return bKeys; return bKeys;
} }
abstract void expand(int extraKeyCount, Object[] extraKeys, Object[] extraValues);
final void expandKeys(int extraKeyCount, Object[] extraKeys) {
int keyCount = getKeyCount();
Object[] newKeys = createKeyStorage(keyCount + extraKeyCount);
System.arraycopy(keys, 0, newKeys, 0, keyCount);
System.arraycopy(extraKeys, 0, newKeys, keyCount, extraKeyCount);
keys = newKeys;
}
/** /**
* Get the total number of key-value pairs, including child pages. * Get the total number of key-value pairs, including child pages.
* *
...@@ -1012,6 +1022,11 @@ public abstract class Page implements Cloneable ...@@ -1012,6 +1022,11 @@ public abstract class Page implements Cloneable
return newPage; return newPage;
} }
@Override
public void expand(int keyCount, Object[] extraKys, Object[] extraValues) {
throw new UnsupportedOperationException();
}
@Override @Override
public long getTotalCount() { public long getTotalCount() {
assert !isComplete() || totalCount == calculateTotalCount() : assert !isComplete() || totalCount == calculateTotalCount() :
...@@ -1324,6 +1339,21 @@ public abstract class Page implements Cloneable ...@@ -1324,6 +1339,21 @@ public abstract class Page implements Cloneable
return newPage; return newPage;
} }
@Override
public void expand(int extraKeyCount, Object[] extraKeys, Object[] extraValues) {
int keyCount = getKeyCount();
expandKeys(extraKeyCount, extraKeys);
if(values != null) {
Object[] newValues = createValueStorage(keyCount + extraKeyCount);
System.arraycopy(values, 0, newValues, 0, keyCount);
System.arraycopy(extraValues, 0, newValues, keyCount, extraKeyCount);
values = newValues;
}
if(isPersistent()) {
recalculateMemory();
}
}
@Override @Override
public long getTotalCount() { public long getTotalCount() {
return getKeyCount(); return getKeyCount();
......
...@@ -134,7 +134,7 @@ public final class MVRTreeMap<V> extends MVMap<SpatialKey, V> { ...@@ -134,7 +134,7 @@ public final class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
int attempt = 0; int attempt = 0;
while(true) { while(true) {
++attempt; ++attempt;
RootReference rootReference = getRoot(); RootReference rootReference = flushAndGetRoot();
Page p = rootReference.root.copy(true); Page p = rootReference.root.copy(true);
V result = operate(p, key, value, decisionMaker); V result = operate(p, key, value, decisionMaker);
if (!p.isLeaf() && p.getTotalCount() == 0) { if (!p.isLeaf() && p.getTotalCount() == 0) {
......
...@@ -61,7 +61,7 @@ public class Transaction { ...@@ -61,7 +61,7 @@ public class Transaction {
*/ */
private static final int STATUS_ROLLED_BACK = 5; private static final int STATUS_ROLLED_BACK = 5;
private static final String STATUS_NAMES[] = { private static final String[] STATUS_NAMES = {
"CLOSED", "OPEN", "PREPARED", "COMMITTED", "ROLLING_BACK", "ROLLED_BACK" "CLOSED", "OPEN", "PREPARED", "COMMITTED", "ROLLING_BACK", "ROLLED_BACK"
}; };
static final int LOG_ID_BITS = 40; static final int LOG_ID_BITS = 40;
...@@ -91,7 +91,7 @@ public class Transaction { ...@@ -91,7 +91,7 @@ public class Transaction {
/** /**
* This is really a transaction identity, because it's not re-used. * This is really a transaction identity, because it's not re-used.
*/ */
public final long sequenceNum; final long sequenceNum;
/* /*
* Transaction state is an atomic composite field: * Transaction state is an atomic composite field:
...@@ -138,12 +138,17 @@ public class Transaction { ...@@ -138,12 +138,17 @@ public class Transaction {
/** /**
* Map on which this transaction is blocked. * Map on which this transaction is blocked.
*/ */
MVMap<?,VersionedValue> blockingMap; private MVMap<?,VersionedValue> blockingMap;
/** /**
* Key in blockingMap on which this transaction is blocked. * Key in blockingMap on which this transaction is blocked.
*/ */
Object blockingKey; private Object blockingKey;
/**
* Whether other transaction(s) are waiting for this to close.
*/
private volatile boolean notificationRequested;
Transaction(TransactionStore store, int transactionId, long sequenceNum, int status, Transaction(TransactionStore store, int transactionId, long sequenceNum, int status,
...@@ -237,7 +242,8 @@ public class Transaction { ...@@ -237,7 +242,8 @@ public class Transaction {
} }
public int getBlockerId() { public int getBlockerId() {
return blockingTransaction == null ? 0 : blockingTransaction.ownerId; Transaction blocker = this.blockingTransaction;
return blocker == null ? 0 : blocker.ownerId;
} }
/** /**
...@@ -390,19 +396,25 @@ public class Transaction { ...@@ -390,19 +396,25 @@ public class Transaction {
public void rollbackToSavepoint(long savepointId) { public void rollbackToSavepoint(long savepointId) {
long lastState = setStatus(STATUS_ROLLING_BACK); long lastState = setStatus(STATUS_ROLLING_BACK);
long logId = getLogId(lastState); long logId = getLogId(lastState);
boolean success;
try { try {
store.rollbackTo(this, logId, savepointId); store.rollbackTo(this, logId, savepointId);
} finally { } finally {
notifyAllWaitingTransactions(); if (notificationRequested) {
notifyAllWaitingTransactions();
}
long expectedState = composeState(STATUS_ROLLING_BACK, logId, hasRollback(lastState)); long expectedState = composeState(STATUS_ROLLING_BACK, logId, hasRollback(lastState));
long newState = composeState(STATUS_OPEN, savepointId, true); long newState = composeState(STATUS_OPEN, savepointId, true);
if (!statusAndLogId.compareAndSet(expectedState, newState)) { do {
throw DataUtils.newIllegalStateException( success = statusAndLogId.compareAndSet(expectedState, newState);
DataUtils.ERROR_TRANSACTION_ILLEGAL_STATE, } while (!success && statusAndLogId.get() == expectedState);
"Transaction {0} concurrently modified " + }
"while rollback to savepoint was in progress", // this is moved outside of finally block to avert masking original exception, if any
transactionId); if (!success) {
} throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TRANSACTION_ILLEGAL_STATE,
"Transaction {0} concurrently modified while rollback to savepoint was in progress",
transactionId);
} }
} }
...@@ -474,7 +486,7 @@ public class Transaction { ...@@ -474,7 +486,7 @@ public class Transaction {
void closeIt() { void closeIt() {
long lastState = setStatus(STATUS_CLOSED); long lastState = setStatus(STATUS_CLOSED);
store.store.deregisterVersionUsage(txCounter); store.store.deregisterVersionUsage(txCounter);
if(hasChanges(lastState) || hasRollback(lastState)) { if((hasChanges(lastState) || hasRollback(lastState)) && notificationRequested) {
notifyAllWaitingTransactions(); notifyAllWaitingTransactions();
} }
} }
...@@ -483,7 +495,10 @@ public class Transaction { ...@@ -483,7 +495,10 @@ public class Transaction {
notifyAll(); notifyAll();
} }
public boolean waitFor(Transaction toWaitFor) { public boolean waitFor(Transaction toWaitFor, MVMap<?,VersionedValue> map, Object key) {
blockingTransaction = toWaitFor;
blockingMap = map;
blockingKey = key;
if (isDeadlocked(toWaitFor)) { if (isDeadlocked(toWaitFor)) {
StringBuilder details = new StringBuilder( StringBuilder details = new StringBuilder(
String.format("Transaction %d has been chosen as a deadlock victim. Details:%n", transactionId)); String.format("Transaction %d has been chosen as a deadlock victim. Details:%n", transactionId));
...@@ -504,7 +519,6 @@ public class Transaction { ...@@ -504,7 +519,6 @@ public class Transaction {
} }
} }
blockingTransaction = toWaitFor;
try { try {
return toWaitFor.waitForThisToEnd(timeoutMillis); return toWaitFor.waitForThisToEnd(timeoutMillis);
} finally { } finally {
...@@ -527,6 +541,7 @@ public class Transaction { ...@@ -527,6 +541,7 @@ public class Transaction {
private synchronized boolean waitForThisToEnd(int millis) { private synchronized boolean waitForThisToEnd(int millis) {
long until = System.currentTimeMillis() + millis; long until = System.currentTimeMillis() + millis;
notificationRequested = true;
long state; long state;
int status; int status;
while((status = getStatus(state = statusAndLogId.get())) != STATUS_CLOSED while((status = getStatus(state = statusAndLogId.get())) != STATUS_CLOSED
...@@ -557,8 +572,15 @@ public class Transaction { ...@@ -557,8 +572,15 @@ public class Transaction {
@Override @Override
public String toString() { public String toString() {
long state = statusAndLogId.get(); return transactionId + "(" + sequenceNum + ") " + stateToString();
return transactionId + "(" + sequenceNum + ") " + STATUS_NAMES[getStatus(state)] + " " + getLogId(state); }
private String stateToString() {
return stateToString(statusAndLogId.get());
}
private static String stateToString(long state) {
return STATUS_NAMES[getStatus(state)] + (hasRollback(state) ? "<" : "") + " " + getLogId(state);
} }
......
...@@ -46,7 +46,7 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> { ...@@ -46,7 +46,7 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> {
/** /**
* The transaction which is used for this map. * The transaction which is used for this map.
*/ */
final Transaction transaction; private final Transaction transaction;
TransactionMap(Transaction transaction, MVMap<K, VersionedValue> map) { TransactionMap(Transaction transaction, MVMap<K, VersionedValue> map) {
this.transaction = transaction; this.transaction = transaction;
...@@ -105,16 +105,16 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> { ...@@ -105,16 +105,16 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> {
long undoLogSize; long undoLogSize;
do { do {
committingTransactions = store.committingTransactions.get(); committingTransactions = store.committingTransactions.get();
mapRootReference = map.getRoot(); mapRootReference = map.flushAndGetRoot();
BitSet opentransactions = store.openTransactions.get(); BitSet opentransactions = store.openTransactions.get();
undoLogRootReferences = new MVMap.RootReference[opentransactions.length()]; undoLogRootReferences = new MVMap.RootReference[opentransactions.length()];
undoLogSize = 0; undoLogSize = 0;
for (int i = opentransactions.nextSetBit(0); i >= 0; i = opentransactions.nextSetBit(i+1)) { for (int i = opentransactions.nextSetBit(0); i >= 0; i = opentransactions.nextSetBit(i+1)) {
MVMap<Long, Object[]> undoLog = store.undoLogs[i]; MVMap<Long, Object[]> undoLog = store.undoLogs[i];
if (undoLog != null) { if (undoLog != null) {
MVMap.RootReference rootReference = undoLog.getRoot(); MVMap.RootReference rootReference = undoLog.flushAndGetRoot();
undoLogRootReferences[i] = rootReference; undoLogRootReferences[i] = rootReference;
undoLogSize += rootReference.root.getTotalCount() + rootReference.getAppendCounter(); undoLogSize += rootReference.getTotalCount();
} }
} }
} while(committingTransactions != store.committingTransactions.get() || } while(committingTransactions != store.committingTransactions.get() ||
...@@ -125,7 +125,7 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> { ...@@ -125,7 +125,7 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> {
// should be considered as committed. // should be considered as committed.
// Subsequent processing uses this snapshot info only. // Subsequent processing uses this snapshot info only.
Page mapRootPage = mapRootReference.root; Page mapRootPage = mapRootReference.root;
long size = mapRootPage.getTotalCount(); long size = mapRootReference.getTotalCount();
// if we are looking at the map without any uncommitted values // if we are looking at the map without any uncommitted values
if (undoLogSize == 0) { if (undoLogSize == 0) {
return size; return size;
...@@ -241,6 +241,16 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> { ...@@ -241,6 +241,16 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> {
return set(key, decisionMaker); return set(key, decisionMaker);
} }
/**
* Appends entry to uderlying map. This method may be used concurrently,
* but latest appended values are not guaranteed to be visible.
* @param key should be higher in map's order than any existing key
* @param value to be appended
*/
public void append(K key, V value) {
map.append(key, VersionedValueUncommitted.getInstance(transaction.log(map.getId(), key, null), value, null));
}
/** /**
* Lock row for the given key. * Lock row for the given key.
* <p> * <p>
...@@ -298,16 +308,12 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> { ...@@ -298,16 +308,12 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> {
assert decision != MVMap.Decision.REPEAT; assert decision != MVMap.Decision.REPEAT;
blockingTransaction = decisionMaker.getBlockingTransaction(); blockingTransaction = decisionMaker.getBlockingTransaction();
if (decision != MVMap.Decision.ABORT || blockingTransaction == null) { if (decision != MVMap.Decision.ABORT || blockingTransaction == null) {
transaction.blockingMap = null;
transaction.blockingKey = null;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
V res = result == null ? null : (V) result.getCurrentValue(); V res = result == null ? null : (V) result.getCurrentValue();
return res; return res;
} }
decisionMaker.reset(); decisionMaker.reset();
transaction.blockingMap = map; } while (blockingTransaction.sequenceNum > sequenceNumWhenStarted || transaction.waitFor(blockingTransaction, map, key));
transaction.blockingKey = key;
} while (blockingTransaction.sequenceNum > sequenceNumWhenStarted || transaction.waitFor(blockingTransaction));
throw DataUtils.newIllegalStateException(DataUtils.ERROR_TRANSACTION_LOCKED, throw DataUtils.newIllegalStateException(DataUtils.ERROR_TRANSACTION_LOCKED,
"Map entry <{0}> with key <{1}> and value {2} is locked by tx {3} and can not be updated by tx {4}" "Map entry <{0}> with key <{1}> and value {2} is locked by tx {3} and can not be updated by tx {4}"
...@@ -669,8 +675,8 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> { ...@@ -669,8 +675,8 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> {
private final boolean includeAllUncommitted; private final boolean includeAllUncommitted;
private X current; private X current;
protected TMIterator(TransactionMap<K,?> transactionMap, K from, K to, boolean includeAllUncommitted) { TMIterator(TransactionMap<K,?> transactionMap, K from, K to, boolean includeAllUncommitted) {
Transaction transaction = transactionMap.transaction; Transaction transaction = transactionMap.getTransaction();
this.transactionId = transaction.transactionId; this.transactionId = transaction.transactionId;
TransactionStore store = transaction.store; TransactionStore store = transaction.store;
MVMap<K, VersionedValue> map = transactionMap.map; MVMap<K, VersionedValue> map = transactionMap.map;
...@@ -683,7 +689,7 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> { ...@@ -683,7 +689,7 @@ public class TransactionMap<K, V> extends AbstractMap<K, V> {
MVMap.RootReference mapRootReference; MVMap.RootReference mapRootReference;
do { do {
committingTransactions = store.committingTransactions.get(); committingTransactions = store.committingTransactions.get();
mapRootReference = map.getRoot(); mapRootReference = map.flushAndGetRoot();
} while (committingTransactions != store.committingTransactions.get()); } while (committingTransactions != store.committingTransactions.get());
// Now we have a snapshot, where mapRootReference points to state of the map // Now we have a snapshot, where mapRootReference points to state of the map
// and committingTransactions mask tells us which of seemingly uncommitted changes // and committingTransactions mask tells us which of seemingly uncommitted changes
......
...@@ -390,7 +390,7 @@ public class TransactionStore { ...@@ -390,7 +390,7 @@ public class TransactionStore {
*/ */
long addUndoLogRecord(int transactionId, long logId, Object[] undoLogRecord) { long addUndoLogRecord(int transactionId, long logId, Object[] undoLogRecord) {
MVMap<Long, Object[]> undoLog = undoLogs[transactionId]; MVMap<Long, Object[]> undoLog = undoLogs[transactionId];
Long undoKey = getOperationId(transactionId, logId); long undoKey = getOperationId(transactionId, logId);
if (logId == 0 && !undoLog.isEmpty()) { if (logId == 0 && !undoLog.isEmpty()) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TOO_MANY_OPEN_TRANSACTIONS, DataUtils.ERROR_TOO_MANY_OPEN_TRANSACTIONS,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论