|
15 | 15 | * limitations under the License. |
16 | 16 | */ |
17 | 17 |
|
| 18 | +#include "checkpoint_test.h" |
| 19 | + |
18 | 20 | #include "config.h" |
19 | 21 |
|
20 | 22 | #include <algorithm> |
|
23 | 25 | #include <vector> |
24 | 26 |
|
25 | 27 | #include "checkpoint.h" |
26 | | -#include "checkpoint_test.h" |
| 28 | +#include "checkpoint_utils.h" |
27 | 29 | #include "configuration.h" |
28 | 30 | #include "ep_vb.h" |
29 | 31 | #include "failover-table.h" |
|
32 | 34 | #include "tests/module_tests/test_helpers.h" |
33 | 35 | #include "thread_gate.h" |
34 | 36 |
|
| 37 | +#include "../mock/mock_dcp_consumer.h" |
| 38 | + |
35 | 39 | #include <engines/ep/src/ep_types.h> |
36 | 40 | #include <gmock/gmock.h> |
37 | 41 | #include <gtest/gtest.h> |
@@ -1235,3 +1239,113 @@ TYPED_TEST(CheckpointTest, |
1235 | 1239 | // Test - second item (duplicate key) should return false. |
1236 | 1240 | EXPECT_FALSE(this->queueNewItem("key")); |
1237 | 1241 | } |
| 1242 | + |
| 1243 | +/* |
| 1244 | + * We always want to close the current open checkpoint on replica-vbuckets |
| 1245 | + * when the Consumer receives the snapshotEnd mutation of a memory-snapshot. |
| 1246 | + */ |
| 1247 | +TEST_F(SingleThreadedCheckpointTest, |
| 1248 | + MB30019_CloseReplicaCheckpointOnMemorySnapshotEnd) { |
| 1249 | + setVBucketStateAndRunPersistTask(vbid, vbucket_state_replica); |
| 1250 | + auto vb = store->getVBuckets().getBucket(vbid); |
| 1251 | + auto* ckptMgr = vb->checkpointManager.get(); |
| 1252 | + ASSERT_NE(nullptr, ckptMgr); |
| 1253 | + |
| 1254 | + // We must have only 1 open checkpoint |
| 1255 | + ASSERT_EQ(1, ckptMgr->getNumCheckpoints()); |
| 1256 | + // We must have only one cursor (the persistence cursor), as there is no |
| 1257 | + // DCP producer for vbid |
| 1258 | + ASSERT_EQ(1, ckptMgr->getNumOfCursors()); |
| 1259 | + // We must have only the checkpoint-open and the vbucket-state meta-items |
| 1260 | + // in the open checkpoint |
| 1261 | + ASSERT_EQ(2, ckptMgr->getNumItems()); |
| 1262 | + ASSERT_EQ(0, ckptMgr->getNumOpenChkItems()); |
| 1263 | + |
| 1264 | + auto consumer = |
| 1265 | + std::make_shared<MockDcpConsumer>(*engine, cookie, "test-consumer"); |
| 1266 | + auto passiveStream = std::static_pointer_cast<MockPassiveStream>( |
| 1267 | + consumer->makePassiveStream( |
| 1268 | + *engine, |
| 1269 | + consumer, |
| 1270 | + "test-passive-stream", |
| 1271 | + 0 /* flags */, |
| 1272 | + 0 /* opaque */, |
| 1273 | + vbid, |
| 1274 | + 0 /* startSeqno */, |
| 1275 | + std::numeric_limits<uint64_t>::max() /* endSeqno */, |
| 1276 | + 0 /* vbUuid */, |
| 1277 | + 0 /* snapStartSeqno */, |
| 1278 | + 0 /* snapEndSeqno */, |
| 1279 | + 0 /* vb_high_seqno */)); |
| 1280 | + |
| 1281 | + const size_t snapshotEnd = 3; |
| 1282 | + // 1) the consumer receives the snapshot-marker |
| 1283 | + SnapshotMarker snapshotMarker( |
| 1284 | + 0 /* opaque */, |
| 1285 | + vbid, |
| 1286 | + 0 /* startSeqno */, |
| 1287 | + snapshotEnd /* endSeqno */, |
| 1288 | + dcp_marker_flag_t::MARKER_FLAG_MEMORY /* flags */); |
| 1289 | + passiveStream->processMarker(&snapshotMarker); |
| 1290 | + |
| 1291 | + // 2) the consumer receives the mutations until (snapshotEnd -1) |
| 1292 | + size_t i = 1; |
| 1293 | + for (; i < snapshotEnd; i++) { |
| 1294 | + // Queue item |
| 1295 | + queued_item qi(new Item(makeStoredDocKey("key_" + std::to_string(i)), |
| 1296 | + 0 /*flags*/, |
| 1297 | + 0 /*expiry*/, |
| 1298 | + "value", |
| 1299 | + 5 /*valueSize*/, |
| 1300 | + PROTOCOL_BINARY_RAW_BYTES, |
| 1301 | + 0 /*cas*/, |
| 1302 | + i /*bySeqno*/, |
| 1303 | + vb->getId())); |
| 1304 | + |
| 1305 | + MutationResponse mutation(std::move(qi), 0 /* opaque */); |
| 1306 | + |
| 1307 | + // PassiveStream::processMutation does 2 things: |
| 1308 | + // 1) setWithMeta (which enqueues the item into the checkpoint) |
| 1309 | + // 2) calls PassiveStream::handleSnapshotEnd (which must close the |
| 1310 | + // open checkpoint if the current mutation is the |
| 1311 | + // snapshot-end) |
| 1312 | + passiveStream->processMutation(&mutation); |
| 1313 | + } |
| 1314 | + // We must have 2 items in the checkpoint now |
| 1315 | + ASSERT_EQ(snapshotEnd - 1, ckptMgr->getNumOpenChkItems()); |
| 1316 | + // We still must have only 1 open checkpoint, as the consumer has not |
| 1317 | + // received the snapshot-end mutation |
| 1318 | + ASSERT_EQ(1, ckptMgr->getNumCheckpoints()); |
| 1319 | + |
| 1320 | + // 3) the consumer receives the snapshotEnd mutation |
| 1321 | + queued_item qi( |
| 1322 | + new Item(makeStoredDocKey("key_" + std::to_string(snapshotEnd)), |
| 1323 | + 0 /*flags*/, |
| 1324 | + 0 /*expiry*/, |
| 1325 | + "value", |
| 1326 | + 5 /*valueSize*/, |
| 1327 | + PROTOCOL_BINARY_RAW_BYTES, |
| 1328 | + 0 /*cas*/, |
| 1329 | + i /*bySeqno*/, |
| 1330 | + vb->getId())); |
| 1331 | + MutationResponse mutation(std::move(qi), 0 /* opaque */); |
| 1332 | + passiveStream->processMutation(&mutation); |
| 1333 | + |
| 1334 | + // The consumer has received the snapshotEnd mutation, now we expect |
| 1335 | + // that a new (empty) open checkpoint has been created. So we must have |
| 1336 | + // 2 checkpoints in total (the closed and the new open one). |
| 1337 | + ASSERT_EQ(2, ckptMgr->getNumCheckpoints()); |
| 1338 | + |
| 1339 | + // Also, the new open checkpoint must be empty (all mutations are in the |
| 1340 | + // closed one) |
| 1341 | + const auto& ckptList = |
| 1342 | + CheckpointManagerTestIntrospector::public_getCheckpointList( |
| 1343 | + *ckptMgr); |
| 1344 | + ASSERT_EQ(ckptList.back()->getId(), ckptList.front()->getId() + 1); |
| 1345 | + ASSERT_EQ(checkpoint_state::CHECKPOINT_CLOSED, |
| 1346 | + ckptList.front()->getState_UNLOCKED()); |
| 1347 | + ASSERT_EQ(snapshotEnd, ckptList.front()->getNumItems()); |
| 1348 | + ASSERT_EQ(checkpoint_state::CHECKPOINT_OPEN, |
| 1349 | + ckptList.back()->getState_UNLOCKED()); |
| 1350 | + ASSERT_EQ(0, ckptList.back()->getNumItems()); |
| 1351 | +} |
0 commit comments