@@ -239,6 +239,12 @@ class SingleThreadedEphemeralPurgerTest : public SingleThreadedKVBucketTest {
239239 for (int vbid = 0 ; vbid < numVbs; ++vbid) {
240240 setVBucketStateAndRunPersistTask (Vbid (vbid), vbucket_state_active);
241241 }
242+
243+ // 'vbid' used for some durability related test
244+ setVBucketStateAndRunPersistTask (
245+ vbid,
246+ vbucket_state_active,
247+ {{" topology" , nlohmann::json::array ({{" active" , " replica" }})}});
242248 }
243249
244250 bool checkAllPurged (uint64_t expPurgeUpto) {
@@ -297,3 +303,82 @@ TEST_F(SingleThreadedEphemeralPurgerTest, PurgeAcrossAllVbuckets) {
297303 EXPECT_GT (numPaused, 2 /* 1 run of 'HTCleaner' and more than 1 run of
298304 'EphTombstoneStaleItemDeleter' */ );
299305}
306+
307+ TEST_F (SingleThreadedEphemeralPurgerTest, HTCleanerSkipsPrepares) {
308+ // Test relies on that the HTCleaner does its work when it runs
309+ ASSERT_EQ (0 , engine->getConfiguration ().getEphemeralMetadataPurgeAge ());
310+
311+ // Store a SyncDelete
312+ auto key = makeStoredDocKey (" key" );
313+ store_item (vbid,
314+ key,
315+ " value" ,
316+ 0 /* exptime*/ ,
317+ {cb::engine_errc::sync_write_pending},
318+ PROTOCOL_BINARY_RAW_BYTES,
319+ cb::durability::Requirements (),
320+ true /* deleted*/ );
321+
322+ auto & vb = *store->getVBucket (vbid);
323+ {
324+ auto res = vb.ht .findForUpdate (key);
325+ ASSERT_TRUE (res.pending );
326+ ASSERT_TRUE (res.pending ->isDeleted ());
327+ ASSERT_EQ (1 , res.pending ->getBySeqno ());
328+ ASSERT_FALSE (res.committed );
329+ }
330+
331+ // Run the HTCleaner
332+ auto * bucket = dynamic_cast <EphemeralBucket*>(store);
333+ bucket->enableTombstonePurgerTask ();
334+ bucket->attemptToFreeMemory (); // This wakes up the HTCleaner
335+ auto & queue = *task_executor->getLpTaskQ ()[NONIO_TASK_IDX];
336+ const std::string expectedTaskName = " Eph tombstone hashtable cleaner" ;
337+ runNextTask (queue, expectedTaskName);
338+
339+ // Core of the test: Verify Prepare still in the HT
340+ {
341+ auto res = vb.ht .findForUpdate (key);
342+ ASSERT_TRUE (res.pending );
343+ ASSERT_EQ (CommittedState::Pending, res.pending ->getCommitted ());
344+ ASSERT_TRUE (res.pending ->isDeleted ());
345+ ASSERT_EQ (1 , res.pending ->getBySeqno ());
346+ ASSERT_FALSE (res.committed );
347+ }
348+
349+ // Proceed with checking that everything behaves as expected at Prepare
350+ // completion.
351+ ASSERT_EQ (ENGINE_SUCCESS,
352+ vb.commit (key, 1 /* prepareSeqno*/ , {}, vb.lockCollections (key)));
353+
354+ // Verify Prepare and Commit in the HT
355+ {
356+ auto res = vb.ht .findForUpdate (key);
357+ ASSERT_TRUE (res.pending );
358+ ASSERT_EQ (CommittedState::PrepareCommitted,
359+ res.pending ->getCommitted ());
360+ ASSERT_TRUE (res.pending ->isDeleted ());
361+ ASSERT_EQ (1 , res.pending ->getBySeqno ());
362+ ASSERT_TRUE (res.committed );
363+ ASSERT_EQ (CommittedState::CommittedViaPrepare,
364+ res.committed ->getCommitted ());
365+ ASSERT_TRUE (res.committed ->isDeleted ());
366+ ASSERT_EQ (2 , res.committed ->getBySeqno ());
367+ }
368+
369+ // Run the StaleItemDeleter (scheduled by the first run of the HTCleaner)
370+ runNextTask (queue, " Eph tombstone stale item deleter" );
371+ // Run the HTCleaner again
372+ bucket->scheduleTombstonePurgerTask ();
373+ bucket->attemptToFreeMemory ();
374+ runNextTask (queue, expectedTaskName);
375+
376+ // Verify that the HTCleaner behaves as expected:
377+ // - Prepare removed as Committed
378+ // - Committed removed as it is a tombstone
379+ {
380+ auto res = vb.ht .findForUpdate (key);
381+ ASSERT_FALSE (res.pending );
382+ ASSERT_FALSE (res.committed );
383+ }
384+ }
0 commit comments