|
36 | 36 | #include <xattr/blob.h> |
37 | 37 | #include <xattr/utils.h> |
38 | 38 |
|
| 39 | +#include <numeric> |
| 40 | + |
39 | 41 | /** |
40 | 42 | * Test fixture for bucket quota tests. Sets quota (max_size) to 200KB and |
41 | 43 | * enables the MemoryTracker. |
@@ -1106,6 +1108,118 @@ TEST_P(STItemPagerTest, ItemPagerEvictionOrder) { |
1106 | 1108 | ; |
1107 | 1109 | } |
1108 | 1110 |
|
| 1111 | +TEST_P(STItemPagerTest, ItemPagerEvictionOrderIsSafe) { |
| 1112 | + // MB-42688: Test that the ordering of the paging visitor comparator is |
| 1113 | + // fixed even if the amount of pageable memory or the vbucket state changes. |
| 1114 | + // This is required to meet the strict weak ordering requirement of |
| 1115 | + // std::sort |
| 1116 | + |
| 1117 | + using Vbid = VBucket::id_type; |
| 1118 | + |
| 1119 | + // 17 is the minimum number of vbs found to demonstrate a segfault |
| 1120 | + // with an unsuitable comparator on linux. This is likely impl dependent. |
| 1121 | + // 18 gives 6 vbs per active/replica/pending |
| 1122 | + std::vector<Vbid> allVBs(18); |
| 1123 | + |
| 1124 | + std::iota(allVBs.begin(), allVBs.end(), 0); |
| 1125 | + |
| 1126 | + for (int i = 0; i < 6; i++) { |
| 1127 | + setVBucketStateAndRunPersistTask(i, vbucket_state_active); |
| 1128 | + setVBucketStateAndRunPersistTask(i + 6, vbucket_state_pending); |
| 1129 | + setVBucketStateAndRunPersistTask(i + 12, vbucket_state_replica); |
| 1130 | + } |
| 1131 | + |
| 1132 | + if (engine->getConfiguration().getBucketType() == "persistent") { |
| 1133 | + // flush all vbs |
| 1134 | + for (const auto& vbid : allVBs) { |
| 1135 | + getEPBucket().flushVBucket(vbid); |
| 1136 | + } |
| 1137 | + } |
| 1138 | + |
| 1139 | + auto& stats = engine->getEpStats(); |
| 1140 | + auto available = std::make_shared<std::atomic<bool>>(); |
| 1141 | + auto& config = engine->getConfiguration(); |
| 1142 | + |
| 1143 | + std::atomic<item_pager_phase> phase{ACTIVE_AND_PENDING_ONLY}; |
| 1144 | + bool isEphemeral = std::get<0>(GetParam()) == "ephemeral"; |
| 1145 | + |
| 1146 | + auto pv = std::make_unique<MockPagingVisitor>( |
| 1147 | + *store, |
| 1148 | + stats, |
| 1149 | + 1.0, |
| 1150 | + available, |
| 1151 | + ITEM_PAGER, |
| 1152 | + false, |
| 1153 | + 1.0, |
| 1154 | + VBucketFilter(allVBs), |
| 1155 | + &phase, |
| 1156 | + isEphemeral, |
| 1157 | + config.getItemEvictionAgePercentage(), |
| 1158 | + config.getItemEvictionFreqCounterAgeThreshold(), |
| 1159 | + PagingVisitor::EvictionPolicy::hifi_mfu); |
| 1160 | + |
| 1161 | + // now test that even with state changes, the comparator sorts the vbuckets |
| 1162 | + // acceptably |
| 1163 | + auto innerComparator = pv->getVBucketComparator(); |
| 1164 | + |
| 1165 | + // wrap the comparator to allow insertion of state changes mid-sort |
| 1166 | + auto comparator = [&innerComparator, this](const Vbid& a, const Vbid& b) { |
| 1167 | + // run the actual comparator |
| 1168 | + auto res = innerComparator(a, b); |
| 1169 | + // now, to _intentionally_ try to break the strict weak ordering, change |
| 1170 | + // the state of one of the vbuckets. If the vbucket comparator is |
| 1171 | + // checking the state each time, this can cause a crash in std::sort |
| 1172 | + for (const auto& vbid : {a, b}) { |
| 1173 | + auto state = store->getVBucket(vbid)->getState(); |
| 1174 | + EXPECT_EQ(ENGINE_SUCCESS, |
| 1175 | + store->setVBucketState(vbid, |
| 1176 | + state == vbucket_state_replica |
| 1177 | + ? vbucket_state_active |
| 1178 | + : vbucket_state_replica, |
| 1179 | + /*transfer*/ false)); |
| 1180 | + } |
| 1181 | + |
| 1182 | + return res; |
| 1183 | + }; |
| 1184 | + |
| 1185 | + // if the vbucket comparator is "safe" and checks the state once, this |
| 1186 | + // sort will work as expected - otherwise it _may_ segfault. |
| 1187 | + // Note: this is not a robust test, variations in the impl of sort |
| 1188 | + // may mean this does _not_ crash even with an unacceptable comparator |
| 1189 | + std::sort(allVBs.begin(), allVBs.end(), comparator); |
| 1190 | + |
| 1191 | + // as a secondary check, directly test the requirements of a strict |
| 1192 | + // weak ordering on the comparator while actively changing |
| 1193 | + // the state of vbuckets. This will fail if the comparator checks |
| 1194 | + // the bucket state on every call. |
| 1195 | + |
| 1196 | + // irreflexivity |
| 1197 | + for (const auto& x : allVBs) { |
| 1198 | + EXPECT_FALSE(comparator(x, x)); |
| 1199 | + } |
| 1200 | + |
| 1201 | + // asymmetry |
| 1202 | + for (const auto& x : allVBs) { |
| 1203 | + for (const auto& y : allVBs) { |
| 1204 | + EXPECT_FALSE(comparator(x, y) && comparator(y, x)); |
| 1205 | + } |
| 1206 | + } |
| 1207 | + |
| 1208 | + // transitivity |
| 1209 | + for (const auto& x : allVBs) { |
| 1210 | + for (const auto& y : allVBs) { |
| 1211 | + for (const auto& z : allVBs) { |
| 1212 | + // x < y && y < z => x < z |
| 1213 | + // equivalent to |
| 1214 | + // !(x < y && y < z) || x < z |
| 1215 | + // if x < y and y < z, it must be true that x < z |
| 1216 | + EXPECT_TRUE(!(comparator(x, y) && comparator(y, z)) || |
| 1217 | + comparator(x, z)); |
| 1218 | + } |
| 1219 | + } |
| 1220 | + } |
| 1221 | +} |
| 1222 | + |
1109 | 1223 | /// Subclass for expiry tests only applicable to persistent buckets. |
1110 | 1224 | class STPersistentExpiryPagerTest : public STExpiryPagerTest {}; |
1111 | 1225 |
|
|
0 commit comments