Skip to content

Commit 67cd140

Browse files
paolococchidaverigby
authored andcommitted
MB-41089: HTTombstonePurger skips Pending stored-values
We would fail with ENOENT at commit/abort otherwise. Change-Id: I4c5ef029d8a0bd998f39882aba0bc87a71ebf78e Reviewed-on: http://review.couchbase.org/c/kv_engine/+/139872 Tested-by: Build Bot <[email protected]> Well-Formed: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 54794ec commit 67cd140

File tree

4 files changed

+100
-0
lines changed

4 files changed

+100
-0
lines changed

engines/ep/src/ephemeral_bucket.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ void EphemeralBucket::enableTombstonePurgerTask() {
246246
ExecutorPool::get()->schedule(tombstonePurgerTask);
247247
}
248248

249+
void EphemeralBucket::scheduleTombstonePurgerTask() {
250+
ExecutorPool::get()->schedule(tombstonePurgerTask);
251+
}
252+
249253
void EphemeralBucket::disableTombstonePurgerTask() {
250254
ExecutorPool::get()->cancel(tombstonePurgerTask->getId());
251255
}

engines/ep/src/ephemeral_bucket.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ class EphemeralBucket : public KVBucket {
119119
*/
120120
void enableTombstonePurgerTask();
121121

122+
/**
123+
* Schedules the Ephemeral Tombstone purger task.
124+
*/
125+
void scheduleTombstonePurgerTask();
126+
122127
/**
123128
* Disables the Ephemeral Tombstone purger task (if enabled).
124129
*/

engines/ep/src/ephemeral_tombstone_purger.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ void EphemeralVBucket::HTTombstonePurger::setCurrentVBucket(VBucket& vb) {
4343

4444
bool EphemeralVBucket::HTTombstonePurger::visit(
4545
const HashTable::HashBucketLock& hbl, StoredValue& v) {
46+
// MB-41089: Never remove Pending Prepares, that would break SyncDelete.
47+
if (v.isPending()) {
48+
// Skip over and continue visiting
49+
return true;
50+
}
51+
4652
auto* osv = v.toOrderedStoredValue();
4753

4854
// MB-31175: Item must have been deleted before this task starts to ensure

engines/ep/tests/module_tests/ephemeral_bucket_test.cc

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)