Skip to content

Commit ba95997

Browse files
committed
Fix severe bugs in BTree::findNext
1 parent 2cf870d commit ba95997

3 files changed

Lines changed: 109 additions & 26 deletions

File tree

include/fs/BTree.h

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -190,29 +190,49 @@ class BTreeBase {
190190
* @return true if a match was found, false if not
191191
*/
192192
bool findNext(T& val) {
193-
btree_node_t<T, N> node;
194-
uint64_t offset;
195-
196-
int i = findNearest(val, node, offset);
197-
if (i > 0)
198-
i--;
193+
btree_node_t<T, N> node = getRootNode();
194+
uint64_t offset = getRootOffset();
195+
bool found = false;
196+
T best;
199197

200198
while (true) {
201-
if (i < node.header.nElements && compare(node.elements[i], val) >= 0) {
202-
// Found a valid next value (either equal or next greater)
203-
val = node.elements[i];
204-
return true;
199+
int low = 0;
200+
int high = node.header.nElements - 1;
201+
int midIdx = -1;
202+
203+
while (low <= high) {
204+
int mid = (low + high) / 2;
205+
int c = compare(node.elements[mid], val);
206+
if (c < 0) {
207+
low = mid + 1;
208+
} else if (c == 0) {
209+
val = node.elements[mid];
210+
return true;
211+
} else {
212+
midIdx = mid;
213+
high = mid - 1;
214+
}
215+
}
216+
217+
if (midIdx != -1) {
218+
if (!found || compare(node.elements[midIdx], best) < 0) {
219+
best = node.elements[midIdx];
220+
found = true;
221+
}
205222
}
206223

207-
// If this node has no children, there is no next element
208224
if (node.header.nChildren == 0)
209-
return false;
225+
break;
210226

211-
// Go to the next child
212-
offset = node.header.childOffsets[i];
227+
offset = node.header.childOffsets[low];
213228
node = getNode(offset);
214-
i = 0;
215229
}
230+
231+
if (found) {
232+
val = best;
233+
return true;
234+
}
235+
return false;
216236
}
217237

218238
/**
@@ -279,11 +299,11 @@ class BTreeBase {
279299
int c = compare(node.elements[mid], val);
280300

281301
if (c < 0)
282-
high = mid - 1;
302+
low = mid + 1;
283303
else if (c == 0) {
284304
return mid;
285305
} else
286-
low = mid + 1;
306+
high = mid - 1;
287307
}
288308

289309
if (node.header.nChildren == 0)
@@ -323,18 +343,18 @@ class BTreeBase {
323343
overwriteNode(offset, node);
324344

325345
} else {
326-
btree_node_t<T, N> child = getNode(node.childOffsets[i]);
346+
btree_node_t<T, N> child = getNode(node.header.childOffsets[i]);
327347

328348
if (child.header.nElements == N) {
329349
uint64_t newChildOffset;
330-
btree_node_t<T, N> newChild = splitChild(node, child, node.childOffsets[i], newChildOffset);
350+
btree_node_t<T, N> newChild = splitChild(node, child, node.header.childOffsets[i], newChildOffset);
331351
if (compare(node.elements[i], element) <= 0) {
332352
child = newChild;
333353
i++;
334354
}
335355
}
336356

337-
insertNonFull(child, node.childOffsets[i], element);
357+
insertNonFull(child, node.header.childOffsets[i], element);
338358
}
339359

340360
}
@@ -452,7 +472,7 @@ class BTreeBase {
452472
int scanNode(const btree_node_t<T, N>& node, const T& element) {
453473
int i = 0;
454474
for (; i < node.header.nElements; i++) {
455-
if (compare(node.elements[i], element) <= 0)
475+
if (compare(node.elements[i], element) >= 0)
456476
break;
457477
}
458478

src/fs/FdHandle.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -551,11 +551,6 @@ std::lock_guard<std::mutex> FdHandle::getLock() const {
551551
return std::lock_guard<std::mutex>(getHandleData(fd).mutex);
552552
}
553553

554-
FdHandle::~FdHandle() {
555-
if (fd >= 0)
556-
getHandleData(fd).decRef();
557-
}
558-
559554
void FdHandle::markToClose() const {
560555
getHandleData(fd).markToClose();
561556
}
@@ -609,6 +604,11 @@ int FdHandle::numReferences() const {
609604
return getHandleData(fd).numReferences();
610605
}
611606

607+
FdHandle::~FdHandle() {
608+
if (fd >= 0)
609+
getHandleData(fd).decRef();
610+
}
611+
612612
FdTransaction::FdTransaction(const FdHandle& handle) : guard(getHandleData(handle.getFd()).mutex), handleData(getHandleData(handle.getFd())) {
613613
}
614614

src/tests/test_BTree.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,66 @@ TEST_F(BTreeBaseTest, FindNext) {
320320
EXPECT_FALSE(tree.findNext(val));
321321
EXPECT_EQ(10000, val);
322322
}
323+
324+
TEST(BTreeFindNext, FindsClosestHigherInChildNode) {
325+
RAMBTree<int, 3> tree(compareInts);
326+
327+
// Insert enough elements to force multiple splits and a multi-level tree.
328+
// Using even numbers so we can probe with odd "gap" queries.
329+
const int numElements = 200;
330+
for (int i = 0; i < numElements; ++i)
331+
tree.insert(i * 2);
332+
333+
// Sanity check: every inserted value must be retrievable via find().
334+
for (int i = 0; i < numElements; ++i) {
335+
int val = i * 2;
336+
ASSERT_TRUE(tree.find(val)) << "find missing for " << (i * 2);
337+
ASSERT_EQ(i * 2, val);
338+
}
339+
340+
// For every odd value in range, findNext must return the next even value.
341+
// Under the previous buggy findNext, the descent skipped the correct child
342+
// and returned an unrelated greater value (or returned false), making this
343+
// test fail.
344+
for (int q = 1; q < numElements * 2 - 1; q += 2) {
345+
int val = q;
346+
EXPECT_TRUE(tree.findNext(val)) << "query=" << q;
347+
EXPECT_EQ(q + 1, val) << "query=" << q;
348+
}
349+
350+
// Exact-match probes must still work.
351+
for (int q = 0; q < numElements * 2; q += 2) {
352+
int val = q;
353+
EXPECT_TRUE(tree.findNext(val)) << "query=" << q;
354+
EXPECT_EQ(q, val) << "query=" << q;
355+
}
356+
357+
// A value strictly larger than every element should not be found.
358+
int val = numElements * 2 + 10;
359+
EXPECT_FALSE(tree.findNext(val));
360+
EXPECT_EQ(numElements * 2 + 10, val);
361+
}
362+
363+
364+
// Regression test: findNext with a sparse outlier in a deep tree. Searching
365+
// in the gap between the densely packed range and the outlier must return
366+
// the outlier (which lives in a different subtree than the descent path of
367+
// the buggy implementation).
368+
TEST(BTreeFindNext, FindsOutlierAcrossSubtrees) {
369+
RAMBTree<int, 3> tree(compareInts);
370+
371+
for (int i = 0; i < 100; ++i)
372+
tree.insert(i * 2);
373+
tree.insert(100000);
374+
375+
int val = 500;
376+
EXPECT_TRUE(tree.findNext(val));
377+
EXPECT_EQ(100000, val);
378+
379+
val = 99999;
380+
EXPECT_TRUE(tree.findNext(val));
381+
EXPECT_EQ(100000, val);
382+
383+
val = 100001;
384+
EXPECT_FALSE(tree.findNext(val));
385+
}

0 commit comments

Comments
 (0)