Skip to content

Commit 3f1b771

Browse files
committed
Fix optimistic B-tree traversal under weak memory ordering
- `BTree.h:1246` dereferences `next` before the parent lease is validated. - During concurrent insert/split, the reader can compute `idx` from one snapshot and read `children[idx]` from a partially visible later snapshot. - On aarch64, weaker store visibility makes this much more plausible: `numElements` can become observable before the matching child slot is safely observable to a stale optimistic reader. - `OptimisticReadWriteLock` is also too weak for this seqlock-style usage, especially around validation and writer publication.
1 parent c3861e0 commit 3f1b771

4 files changed

Lines changed: 24 additions & 6 deletions

File tree

src/include/souffle/datastructure/BTree.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,12 @@ class btree {
12411241

12421242
// get next pointer
12431243
auto next = cur->getChild(idx);
1244+
if (next == nullptr) {
1245+
if (cur->lock.validate(cur_lease)) {
1246+
assert(false && "B-tree inner node has null child");
1247+
}
1248+
return insert(k, hints);
1249+
}
12441250

12451251
// get lease on next level
12461252
auto next_lease = next->lock.start_read();

src/include/souffle/datastructure/BTreeDelete.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,12 @@ class btree_delete {
12941294

12951295
// get next pointer
12961296
auto next = cur->getChild(idx);
1297+
if (next == nullptr) {
1298+
if (cur->lock.validate(cur_lease)) {
1299+
assert(false && "B-tree inner node has null child");
1300+
}
1301+
return insert(k, hints);
1302+
}
12971303

12981304
// get lease on next level
12991305
auto next_lease = next->lock.start_read();

src/include/souffle/datastructure/LambdaBTree.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ class LambdaBTree : public btree<Key, Comparator, Allocator, blockSize, SearchSt
198198

199199
// get next pointer
200200
auto next = cur->getChild(idx);
201+
if (next == nullptr) {
202+
if (cur->lock.validate(cur_lease)) {
203+
assert(false && "B-tree inner node has null child");
204+
}
205+
return insert(k, hints, f);
206+
}
201207

202208
// get lease on next level
203209
auto next_lease = next->lock.start_read();

src/include/souffle/utility/ParallelUtil.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ class OptimisticReadWriteLock {
444444
bool validate(const Lease& lease) {
445445
// check whether version number has changed in the mean-while
446446
std::atomic_thread_fence(std::memory_order_acquire);
447-
return lease.version == version.load(std::memory_order_relaxed);
447+
return lease.version == version.load(std::memory_order_acquire);
448448
}
449449

450450
/**
@@ -466,14 +466,14 @@ class OptimisticReadWriteLock {
466466
detail::Waiter wait;
467467

468468
// set last bit => make it odd
469-
auto v = version.fetch_or(0x1, std::memory_order_acquire);
469+
auto v = version.fetch_or(0x1, std::memory_order_acq_rel);
470470

471471
// check for concurrent writes
472472
while ((v & 0x1) == 1) {
473473
// wait for a moment
474474
wait();
475475
// get an updated version
476-
v = version.fetch_or(0x1, std::memory_order_acquire);
476+
v = version.fetch_or(0x1, std::memory_order_acq_rel);
477477
}
478478

479479
// done
@@ -486,7 +486,7 @@ class OptimisticReadWriteLock {
486486
* @return true if write permission has been granted, false otherwise.
487487
*/
488488
bool try_start_write() {
489-
auto v = version.fetch_or(0x1, std::memory_order_acquire);
489+
auto v = version.fetch_or(0x1, std::memory_order_acq_rel);
490490
return !(v & 0x1);
491491
}
492492

@@ -499,7 +499,7 @@ class OptimisticReadWriteLock {
499499
* be granted, false otherwise.
500500
*/
501501
bool try_upgrade_to_write(const Lease& lease) {
502-
auto v = version.fetch_or(0x1, std::memory_order_acquire);
502+
auto v = version.fetch_or(0x1, std::memory_order_acq_rel);
503503

504504
// check whether write privileges have been gained
505505
if (v & 0x1) return false; // there is another writer already
@@ -539,7 +539,7 @@ class OptimisticReadWriteLock {
539539
* @return true if so, false otherwise
540540
*/
541541
bool is_write_locked() const {
542-
return version & 0x1;
542+
return version.load(std::memory_order_relaxed) & 0x1;
543543
}
544544
};
545545

0 commit comments

Comments
 (0)