Skip to content

Commit 0df2087

Browse files
jameseh96daverigby
authored andcommitted
MB-43055: [BP] Ensure ItemPager available is not left set to false
Backport of ac69da7 http://review.couchbase.org/c/kv_engine/+/141367 The ItemPager exited early, without scheduling a PagingVisitor, if current memory usage had already dropped below the low watermark by the time the ItemPager task was run. However, this was done _after_ the `available` flag had been set to false. This was an issue, as the flag is only set back to true by `PagingVisitor::complete()` - but no PagingVisitor was scheduled. Fix by exiting before `available` is altered. Change-Id: Iee11632ff95c7f507935098cc4e75ad58b7e160b Reviewed-on: http://review.couchbase.org/c/kv_engine/+/142986 Well-Formed: Build Bot <[email protected]> Tested-by: James Harrison <[email protected]> Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 6dfd920 commit 0df2087

File tree

3 files changed

+93
-12
lines changed

3 files changed

+93
-12
lines changed

engines/ep/src/fakes/fake_executorpool.h

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,18 @@ class CheckedExecutor : public ExecutorThread {
164164
// Configure a checker to run, some tasks are subtly different
165165
if (getTaskName() == "Snapshotting vbucket states" ||
166166
getTaskName() == "Removing closed unreferenced checkpoints from memory" ||
167-
getTaskName() == "Paging out items." ||
168167
getTaskName() == "Paging expired items." ||
169168
getTaskName() == "Adjusting hash table sizes." ||
170169
getTaskName() == "Generating access log") {
171170
checker = [=](bool taskRescheduled) {
172171
// These tasks all schedule one other task
173172
this->oneExecutes(taskRescheduled, 1);
174173
};
174+
} else if (getTaskName() == "Paging out items.") {
175+
checker = [=](bool taskRescheduled) {
176+
// This task _may_ schedule a single task.
177+
this->oneExecutes(taskRescheduled, /*min*/ 0, /*max*/ 1);
178+
};
175179
} else {
176180
checker = [=](bool taskRescheduled) {
177181
this->oneExecutes(taskRescheduled, 0);
@@ -217,17 +221,33 @@ class CheckedExecutor : public ExecutorThread {
217221
* - request itself to be rescheduled
218222
* - schedule other tasks (expectedToBeScheduled)
219223
*/
220-
void oneExecutes(bool rescheduled, int expectedToBeScheduled) {
221-
if (rescheduled) {
222-
// One task executed and was rescheduled, account for it.
223-
expectedToBeScheduled++;
224+
void oneExecutes(bool rescheduled,
225+
int minExpectedToBeScheduled,
226+
int maxExpectedToBeScheduled) {
227+
auto expected = preFutureQueueSize + preReadyQueueSize;
228+
auto actual = queue.getFutureQueueSize() + queue.getReadyQueueSize();
229+
230+
if (!rescheduled) {
231+
// the task did _not_ reschedule itself, so expect one fewer task
232+
expected--;
224233
}
225234

226235
// Check that the new sizes of the future and ready tally given
227236
// one executed and n were scheduled as a side effect.
228-
EXPECT_EQ((preFutureQueueSize + preReadyQueueSize) - 1,
229-
(queue.getFutureQueueSize() + queue.getReadyQueueSize()) -
230-
expectedToBeScheduled);
237+
238+
// there should now be _at least_ minExpectedToBeScheduled extra
239+
// tasks
240+
EXPECT_GE(actual, expected + minExpectedToBeScheduled);
241+
// there should now be _no more than_ maxExpectedToBeScheduled extra
242+
// tasks
243+
EXPECT_LE(actual, expected + maxExpectedToBeScheduled);
244+
}
245+
246+
void oneExecutes(bool rescheduled, int expectedToBeScheduled) {
247+
// expect _exactly_ the specified number of tasks to be scheduled
248+
oneExecutes(rescheduled,
249+
/*min*/ expectedToBeScheduled,
250+
/*max*/ expectedToBeScheduled);
231251
}
232252

233253
/*

engines/ep/src/item_pager.cc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,12 +563,22 @@ bool ItemPager::run(void) {
563563
}
564564

565565
if (current <= lower) {
566+
// doEvict may have been set to ensure eviction would continue until the
567+
// low watermark was reached - it now has, so clear the flag.
566568
doEvict = false;
569+
// If a PagingVisitor were to be created now, it would visit vbuckets
570+
// but not try to evict anything. Stop now instead.
571+
return true;
567572
}
568573

569-
bool inverse = true;
570-
if (((current > upper) || doEvict || wasNotified) &&
571-
(*available).compare_exchange_strong(inverse, false)) {
574+
if ((current > upper) || doEvict || wasNotified) {
575+
bool inverse = true;
576+
if (!(*available).compare_exchange_strong(inverse, false)) {
577+
// available != true, another PagingVisitor exists and is still
578+
// running. Don't create another.
579+
return true;
580+
}
581+
// Note: available is reset to true by PagingVisitor::complete()
572582
if (kvBucket->getItemEvictionPolicy() == VALUE_ONLY) {
573583
doEvict = true;
574584
}

engines/ep/tests/module_tests/item_pager_test.cc

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ class STBucketQuotaTest : public STParameterizedBucketTest {
163163
return count;
164164
}
165165

166-
void populateUntilAboveHighWaterMark(uint16_t vbid) {
166+
int populateUntilAboveHighWaterMark(uint16_t vbid) {
167167
bool populate = true;
168168
int count = 0;
169169
auto& stats = engine->getEpStats();
@@ -177,6 +177,7 @@ class STBucketQuotaTest : public STParameterizedBucketTest {
177177
populate = stats.getEstimatedTotalMemoryUsed() <=
178178
stats.mem_high_wat.load();
179179
}
180+
return count;
180181
}
181182

182183
/**
@@ -940,6 +941,56 @@ TEST_P(STItemPagerTest, ActiveEvictedIfReplicaEvictionInsufficient) {
940941
EXPECT_LT(replicaRR, 5);
941942
}
942943

944+
TEST_P(STItemPagerTest, MB43055_MemUsedDropDoesNotBreakEviction) {
945+
// MB-43055: Test that having current memory usage drop below the low
946+
// watermark before the item pager runs does not prevent future item
947+
// pager runs.
948+
949+
if (std::get<1>(GetParam()) == "fail_new_data") {
950+
// items are not auto-deleted, so the ItemPager does not run.
951+
return;
952+
}
953+
954+
setVBucketStateAndRunPersistTask(vbid, vbucket_state_active);
955+
956+
// this triggers eviction, scheduling the ItemPager task
957+
auto itemCount = populateUntilAboveHighWaterMark(vbid);
958+
959+
// now delete some items to lower memory usage
960+
for (int i = 0; i < itemCount; i++) {
961+
auto key = makeStoredDocKey("key_" + std::to_string(i));
962+
uint64_t cas = 0;
963+
mutation_descr_t mutation_descr;
964+
EXPECT_EQ(ENGINE_SUCCESS,
965+
store->deleteItem(key,
966+
cas,
967+
vbid,
968+
cookie,
969+
/*itemMeta*/ nullptr,
970+
mutation_descr));
971+
}
972+
973+
auto& stats = engine->getEpStats();
974+
// confirm we are now below the low watermark, and can test the item pager
975+
// behaviour
976+
ASSERT_LT(stats.getEstimatedTotalMemoryUsed(), stats.mem_low_wat.load());
977+
978+
auto& lpNonioQ = *task_executor->getLpTaskQ()[NONIO_TASK_IDX];
979+
// run the item pager. It should _not_ create and schedule a PagingVisitor
980+
runNextTask(lpNonioQ, "Paging out items.");
981+
982+
EXPECT_EQ(0, lpNonioQ.getReadyQueueSize());
983+
EXPECT_EQ(initialNonIoTasks, lpNonioQ.getFutureQueueSize());
984+
985+
// populate again, and confirm this time that the item pager does shedule
986+
// a paging visitor
987+
populateUntilAboveHighWaterMark(vbid);
988+
989+
runNextTask(lpNonioQ, "Paging out items.");
990+
991+
runNextTask(lpNonioQ, "Item pager on vb 0");
992+
}
993+
943994
/**
944995
* Test fixture for Ephemeral-only item pager tests.
945996
*/

0 commit comments

Comments
 (0)