Skip to content

Commit 41426c6

Browse files
committed
MB-42688: Merge branch 'couchbase/alice' into mad-hatter
* commit '3b0344840': MB-42688: Do not check vbucket state on each comparator call Change-Id: Ia7a9fbda15849d0d3af5d9a51af503da5e017c21
2 parents 9f74182 + 3b03448 commit 41426c6

File tree

2 files changed

+120
-13
lines changed

2 files changed

+120
-13
lines changed

engines/ep/src/paging_visitor.cc

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -334,27 +334,27 @@ void PagingVisitor::complete() {
334334

335335
std::function<bool(const Vbid&, const Vbid&)>
336336
PagingVisitor::getVBucketComparator() const {
337-
// Get the pageable mem used of each vb _once_ and cache it.
338-
// Fetching these values repeatedly in the comparator could cause issues as
339-
// the values can change _during_ a given sort call.
337+
// Get the pageable mem used and state of each vb _once_ and cache it.
338+
// Fetching these values repeatedly in the comparator could cause issues
339+
// as the values can change _during_ a given sort call.
340340

341-
std::map<Vbid, size_t> pageableMemUsed;
341+
std::map<Vbid, std::pair<bool, size_t>> stateAndPageableMemUsed;
342342

343343
for (const auto& vbid : store.getVBuckets().getBuckets()) {
344344
auto vb = store.getVBucket(vbid);
345-
pageableMemUsed[vbid] = vb ? vb->getPageableMemUsage() : 0;
345+
if (vb) {
346+
stateAndPageableMemUsed[vbid] = {
347+
vb->getState() == vbucket_state_replica,
348+
vb->getPageableMemUsage()};
349+
}
346350
}
347351

348-
return [pageableMemUsed = std::move(pageableMemUsed), &store = store](
349-
const Vbid& a, const Vbid& b) mutable {
350-
auto vbA = store.getVBucket(a);
351-
auto vbB = store.getVBucket(b);
352-
bool aReplica = vbA && vbA->getState() == vbucket_state_replica;
353-
bool bReplica = vbB && vbB->getState() == vbucket_state_replica;
352+
return [stateAndPageableMemUsed = std::move(stateAndPageableMemUsed)](
353+
const Vbid& a,
354+
const Vbid& b) mutable {
354355
// sort replicas before all other vbucket states, then sort by
355356
// pageableMemUsed
356-
return std::make_pair(aReplica, pageableMemUsed[a]) >
357-
std::make_pair(bReplica, pageableMemUsed[b]);
357+
return stateAndPageableMemUsed[a] > stateAndPageableMemUsed[b];
358358
};
359359
}
360360

engines/ep/tests/module_tests/item_pager_test.cc

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,6 +1094,113 @@ TEST_P(STItemPagerTest, ItemPagerEvictionOrder) {
10941094
;
10951095
}
10961096

1097+
TEST_P(STItemPagerTest, ItemPagerEvictionOrderIsSafe) {
1098+
// MB-42688: Test that the ordering of the paging visitor comparator is
1099+
// fixed even if the amount of pageable memory or the vbucket state changes.
1100+
// This is required to meet the strict weak ordering requirement of
1101+
// std::sort
1102+
1103+
// 17 is the minimum number of vbs found to demonstrate a segfault
1104+
// with an unsuitable comparator on linux. This is likely impl dependent.
1105+
// 18 gives 6 vbs per active/replica/pending
1106+
resetEngineAndWarmup("max_vbuckets=18");
1107+
std::vector<Vbid> allVBs(18);
1108+
1109+
for (int i = 0; i < 18; i++) {
1110+
allVBs[i] = Vbid(i);
1111+
}
1112+
1113+
1114+
for (int i = 0; i < 6; i++) {
1115+
setVBucketStateAndRunPersistTask(Vbid(i), vbucket_state_active);
1116+
setVBucketStateAndRunPersistTask(Vbid(i + 6), vbucket_state_pending);
1117+
setVBucketStateAndRunPersistTask(Vbid(i + 12), vbucket_state_replica);
1118+
}
1119+
1120+
if (engine->getConfiguration().getBucketType() == "persistent") {
1121+
// flush all vbs
1122+
for (const auto& vbid : allVBs) {
1123+
dynamic_cast<EPBucket&>(*store).flushVBucket(vbid);
1124+
}
1125+
}
1126+
1127+
auto& stats = engine->getEpStats();
1128+
auto available = std::make_shared<std::atomic<bool>>();
1129+
auto& config = engine->getConfiguration();
1130+
1131+
auto pv = std::make_unique<MockPagingVisitor>(
1132+
*store,
1133+
stats,
1134+
EvictionRatios{1.0 /*active*/, 1.0 /*replica*/},
1135+
available,
1136+
ITEM_PAGER,
1137+
false,
1138+
VBucketFilter(allVBs),
1139+
config.getItemEvictionAgePercentage(),
1140+
config.getItemEvictionFreqCounterAgeThreshold());
1141+
1142+
// now test that even with state changes, the comparator sorts the vbuckets
1143+
// acceptably
1144+
auto innerComparator = pv->getVBucketComparator();
1145+
1146+
// wrap the comparator to allow insertion of state changes mid-sort
1147+
auto comparator = [&innerComparator, this](const Vbid& a, const Vbid& b) {
1148+
// run the actual comparator
1149+
auto res = innerComparator(a, b);
1150+
// now, to _intentionally_ try to break the strict weak ordering, change
1151+
// the state of one of the vbuckets. If the vbucket comparator is
1152+
// checking the state each time, this can cause a crash in std::sort
1153+
for (const auto& vbid : {a, b}) {
1154+
auto state = store->getVBucket(vbid)->getState();
1155+
EXPECT_EQ(ENGINE_SUCCESS,
1156+
store->setVBucketState(vbid,
1157+
state == vbucket_state_replica
1158+
? vbucket_state_active
1159+
: vbucket_state_replica));
1160+
}
1161+
1162+
return res;
1163+
};
1164+
1165+
// if the vbucket comparator is "safe" and checks the state once, this
1166+
// sort will work as expected - otherwise it _may_ segfault.
1167+
// Note: this is not a robust test, variations in the impl of sort
1168+
// may mean this does _not_ crash even with an unacceptable comparator
1169+
std::sort(allVBs.begin(), allVBs.end(), comparator);
1170+
1171+
// as a secondary check, directly test the requirements of a strict
1172+
// weak ordering on the comparator while actively changing
1173+
// the state of vbuckets. This will fail if the comparator checks
1174+
// the bucket state on every call.
1175+
1176+
// irreflexivity
1177+
for (const auto& x : allVBs) {
1178+
EXPECT_FALSE(comparator(x, x));
1179+
}
1180+
1181+
// asymmetry
1182+
for (const auto& x : allVBs) {
1183+
for (const auto& y : allVBs) {
1184+
EXPECT_FALSE(comparator(x, y) && comparator(y, x));
1185+
}
1186+
}
1187+
1188+
// transitivity
1189+
for (const auto& x : allVBs) {
1190+
for (const auto& y : allVBs) {
1191+
for (const auto& z : allVBs) {
1192+
// x < y && y < z => x < z
1193+
// equivalent to
1194+
// !(x < y && y < z) || x < z
1195+
// if x < y and y < z, it must be true that x < z
1196+
EXPECT_TRUE(!(comparator(x, y) && comparator(y, z)) ||
1197+
comparator(x, z));
1198+
}
1199+
}
1200+
}
1201+
}
1202+
1203+
10971204
/**
10981205
* Test fixture for Ephemeral-only item pager tests.
10991206
*/

0 commit comments

Comments
 (0)