* search. The tree must be latched.
*/
if (!treeClaimAcquired) {
if (!_treeHolder.claim(treeWriterClaimRequired)) {
Debug.$assert0.t(false);
throw new InUseException("Thread " + Thread.currentThread().getName()
+ " failed to get writer claim on " + _tree);
}
treeClaimAcquired = true;
}
//
// Need to redo this check now that we have a
// claim on the Tree.
//
checkLevelCache();
long pageAddr1 = _tree.getRootPageAddr();
long pageAddr2 = pageAddr1;
for (int level = _cacheDepth; --level >= 0;) {
final LevelCache lc = _levelCache[level];
lc.initRemoveFields();
depth = level;
final int foundAt1 = searchLevel(key1, true, pageAddr1, level, true);
int foundAt2 = -1;
//
// Note: this buffer now has a writer claim on it.
//
Buffer buffer = lc._buffer;
lc._flags |= LEFT_CLAIMED;
lc._leftBuffer = buffer;
lc._leftFoundAt = foundAt1;
boolean samePage = pageAddr2 == pageAddr1;
if (samePage) {
foundAt2 = buffer.findKey(key2);
if (!buffer.isAfterRightEdge(foundAt2)) {
lc._rightBuffer = buffer;
lc._rightFoundAt = foundAt2;
} else {
pageAddr2 = buffer.getRightSibling();
samePage = false;
}
}
if (!samePage) {
//
// Since we are spanning pages we need an
// exclusive claim on the tree to prevent
// an insertion from propagating upward through
// the deletion range.
//
if (!treeWriterClaimRequired) {
treeWriterClaimRequired = true;
if (!_treeHolder.upgradeClaim()) {
throw RetryException.SINGLE;
}
}
foundAt2 = searchLevel(key2, false, pageAddr2, level, true);
buffer = lc._buffer;
lc._flags |= RIGHT_CLAIMED;
lc._rightBuffer = buffer;
lc._rightFoundAt = foundAt2;
pageAddr2 = buffer.getPageAddress();
}
if (lc._leftBuffer.isIndexPage()) {
Debug.$assert0.t(lc._rightBuffer.isIndexPage() && depth > 0);
//
// Come down to the left of the key.
//
final int p1 = lc._leftBuffer.previousKeyBlock(foundAt1);
final int p2 = lc._rightBuffer.previousKeyBlock(foundAt2);
Debug.$assert0.t(p1 != -1 && p2 != -1);
pageAddr1 = lc._leftBuffer.getPointer(p1);
pageAddr2 = lc._rightBuffer.getPointer(p2);
} else {
Debug.$assert0.t(depth == 0);
break;
}
}
LevelCache lc = _levelCache[0];
if (removeOnlyAntiValue
& !isKeyRangeAntiValue(lc._leftBuffer, lc._leftFoundAt, lc._rightBuffer, lc._rightFoundAt)) {
result = false;
break;
}
if (fetchFirst) {
removeFetchFirst(lc._leftBuffer, lc._leftFoundAt, lc._rightBuffer, lc._rightFoundAt);
}
//
// We have fully delineated the subtree that
// needs to be removed. Now walk down the tree,
// stitching together the pages where necessary.
//
_tree.bumpGeneration();
final long timestamp = timestamp();
for (int level = _cacheDepth; --level >= 0;) {
lc = _levelCache[level];
final Buffer buffer1 = lc._leftBuffer;
final Buffer buffer2 = lc._rightBuffer;
int foundAt1 = lc._leftFoundAt;
int foundAt2 = lc._rightFoundAt;
foundAt1 &= P_MASK;
foundAt2 &= P_MASK;
boolean needsReindex = false;
buffer1.writePageOnCheckpoint(timestamp);
if (buffer1 != buffer2) {
buffer2.writePageOnCheckpoint(timestamp);
//
// Deletion spans multiple pages at this level.
// We will need to join or rebalance the pages.
//
final long leftGarbagePage = buffer1.getRightSibling();
_key.copyTo(_spareKey1);
// Before we remove the records in this range, we
// need to recover any LONG_RECORD pointers that
// are associated with keys in this range.
_volume.getStructure().harvestLongRecords(buffer1, foundAt1, Integer.MAX_VALUE);
_volume.getStructure().harvestLongRecords(buffer2, 0, foundAt2);
Debug.$assert0.t(_tree.isOwnedAsWriterByMe() && buffer1.isOwnedAsWriterByMe()
&& buffer2.isOwnedAsWriterByMe());
boolean rebalanced = false;
try {
rebalanced = buffer1.join(buffer2, foundAt1, foundAt2, _spareKey1, _spareKey2,
_joinPolicy);
} catch (final RebalanceException rbe) {
rebalanceSplit(lc);
level++;
continue;
}
if (buffer1.isDataPage()) {
_tree.bumpChangeCount();
}
buffer1.setDirtyAtTimestamp(timestamp);
buffer2.setDirtyAtTimestamp(timestamp);
final long rightGarbagePage = buffer1.getRightSibling();
if (rightGarbagePage != leftGarbagePage) {
// here we just remember the page boundaries
// that will need to be deallocated.
lc._deallocLeftPage = leftGarbagePage;
lc._deallocRightPage = rightGarbagePage;
deallocationRequired = true;
}
if (rebalanced) {
//
// If the join operation was not able to
// coalesce the two pages into one, then we need
// to re-index the new first key of the second
// page.
//
// We have either a quick way to do this or a
// more complex way. If there is a single parent
// page in the index for the two re-balanced
// pages, and if the key to be reinserted fits
// in that parent page, then all we need to do
// is insert it. Otherwise, we will need to
// split the page above us, and that will
// potentially result in additional buffer
// reservations. Because that could force a
// retry at a bad time, in that case we defer
// the re-insertion of the index key until
// after all the current claims are released.
//
needsReindex = true;
if (level < _cacheDepth - 1) {
final LevelCache parentLc = _levelCache[level + 1];
final Buffer buffer = parentLc._leftBuffer;
Debug.$assert0.t(buffer != null);
if (parentLc._rightBuffer == buffer) {
final int foundAt = buffer.findKey(_spareKey1);
Debug.$assert0.t((foundAt & EXACT_MASK) == 0);
// Try it the simple way
_value.setPointerValue(buffer2.getPageAddress());
_value.setPointerPageType(buffer2.getPageType());
_rawValueWriter.init(_value);
final int fit = buffer.putValue(_spareKey1, _rawValueWriter, foundAt, false);
// If it worked then we're done.
if (fit != -1) {
needsReindex = false;
buffer.setDirtyAtTimestamp(timestamp);
}
}
}
if (needsReindex) {
_spareKey1.copyTo(_spareKey2);
_value.setPointerValue(buffer2.getPageAddress());
_value.setPointerPageType(buffer2.getPageType());
storeInternal(_spareKey2, _value, level + 1, StoreOptions.NONE);
needsReindex = false;
}
}
result = true;
} else if (foundAt1 != foundAt2) {
Debug.$assert0.t(foundAt2 > foundAt1);
_key.copyTo(_spareKey1);
//
// Before we remove these records, we need to
// recover any LONG_RECORD pointers that may be
// associated with keys in this range.
//
_volume.getStructure().harvestLongRecords(buffer1, foundAt1, foundAt2);
result |= buffer1.removeKeys(foundAt1, foundAt2, _spareKey1);
if (buffer1.isDataPage() && result) {
_tree.bumpChangeCount();
}
buffer1.setDirtyAtTimestamp(timestamp);
}
if (level < _cacheDepth - 1) {
removeKeyRangeReleaseLevel(level + 1);
}
}
break;
} catch (final RetryException re) {
// handled below by releasing claims and retrying
} finally {
//
// Release all buffers.
//
for (int level = _cacheDepth; --level >= depth;) {
removeKeyRangeReleaseLevel(level);
}
if (treeClaimAcquired) {
_treeHolder.release();
treeClaimAcquired = false;
}
}
/*
* Having released all prior claims, now acquire an exclusive
* claim on the Tree.
*/
if (treeWriterClaimRequired) {
if (!_treeHolder.claim(true)) {
Debug.$assert0.t(false);
throw new InUseException("Thread " + Thread.currentThread().getName()
+ " failed to get reader claim on " + _tree);
}
treeClaimAcquired = true;
}
}