Skip to content

Commit ac69da7

Browse files
committed
MB-43055: Ensure ItemPager available is not left set to false
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/+/141367 Well-Formed: Build Bot <[email protected]> Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 675007e commit ac69da7

File tree

3 files changed

+94
-17
lines changed

3 files changed

+94
-17
lines changed

engines/ep/src/fakes/fake_executorpool.h

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,18 @@ class CheckedExecutor : public ExecutorThread {
175175
// Configure a checker to run, some tasks are subtly different
176176
if (getTaskName() == "Snapshotting vbucket states" ||
177177
getTaskName() == "Removing closed unreferenced checkpoints from memory" ||
178-
getTaskName() == "Paging out items." ||
179178
getTaskName() == "Paging expired items." ||
180179
getTaskName() == "Adjusting hash table sizes." ||
181180
getTaskName() == "Generating access log") {
182181
checker = [=](bool taskRescheduled) {
183182
// These tasks all schedule one other task
184183
this->oneExecutes(taskRescheduled, 1);
185184
};
185+
} else if (getTaskName() == "Paging out items.") {
186+
checker = [=](bool taskRescheduled) {
187+
// This task _may_ schedule a single task.
188+
this->oneExecutes(taskRescheduled, /*min*/ 0, /*max*/ 1);
189+
};
186190
} else {
187191
checker = [=](bool taskRescheduled) {
188192
this->oneExecutes(taskRescheduled, 0);
@@ -226,17 +230,33 @@ class CheckedExecutor : public ExecutorThread {
226230
* - request itself to be rescheduled
227231
* - schedule other tasks (expectedToBeScheduled)
228232
*/
229-
void oneExecutes(bool rescheduled, int expectedToBeScheduled) {
230-
if (rescheduled) {
231-
// One task executed and was rescheduled, account for it.
232-
expectedToBeScheduled++;
233+
void oneExecutes(bool rescheduled,
234+
int minExpectedToBeScheduled,
235+
int maxExpectedToBeScheduled) {
236+
auto expected = preFutureQueueSize + preReadyQueueSize;
237+
auto actual = queue.getFutureQueueSize() + queue.getReadyQueueSize();
238+
239+
if (!rescheduled) {
240+
// the task did _not_ reschedule itself, so expect one fewer task
241+
expected--;
233242
}
234243

235244
// Check that the new sizes of the future and ready tally given
236245
// one executed and n were scheduled as a side effect.
237-
EXPECT_EQ((preFutureQueueSize + preReadyQueueSize) - 1,
238-
(queue.getFutureQueueSize() + queue.getReadyQueueSize()) -
239-
expectedToBeScheduled);
246+
247+
// there should now be _at least_ minExpectedToBeScheduled extra
248+
// tasks
249+
EXPECT_GE(actual, expected + minExpectedToBeScheduled);
250+
// there should now be _no more than_ maxExpectedToBeScheduled extra
251+
// tasks
252+
EXPECT_LE(actual, expected + maxExpectedToBeScheduled);
253+
}
254+
255+
void oneExecutes(bool rescheduled, int expectedToBeScheduled) {
256+
// expect _exactly_ the specified number of tasks to be scheduled
257+
oneExecutes(rescheduled,
258+
/*min*/ expectedToBeScheduled,
259+
/*max*/ expectedToBeScheduled);
240260
}
241261

242262
/*

engines/ep/src/item_pager.cc

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,23 +105,29 @@ bool ItemPager::run(void) {
105105
double lower = engine.getKVBucket()->getPageableMemLowWatermark();
106106

107107
if (current <= lower) {
108+
// doEvict may have been set to ensure eviction would continue until the
109+
// low watermark was reached - it now has, so clear the flag.
108110
doEvict = false;
111+
// If a PagingVisitor were to be created now, it would visit vbuckets
112+
// but not try to evict anything. Stop now instead.
113+
return true;
109114
}
110115

111-
bool inverse = true;
112-
if (((current > upper) || doEvict || wasNotified) &&
113-
(*available).compare_exchange_strong(inverse, false)) {
116+
if ((current > upper) || doEvict || wasNotified) {
117+
bool inverse = true;
118+
if (!(*available).compare_exchange_strong(inverse, false)) {
119+
// available != true, another PagingVisitor exists and is still
120+
// running. Don't create another.
121+
return true;
122+
}
123+
// Note: available is reset to true by PagingVisitor::complete()
124+
114125
if (kvBucket->getItemEvictionPolicy() == EvictionPolicy::Value) {
115126
doEvict = true;
116127
}
117128

118129
++stats.pagerRuns;
119130

120-
if (current <= lower) {
121-
// early exit - no need to run a paging visitor
122-
return true;
123-
}
124-
125131
VBucketFilter replicaFilter;
126132
VBucketFilter activePendingFilter;
127133

engines/ep/tests/module_tests/item_pager_test.cc

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class STBucketQuotaTest : public STParameterizedBucketTest {
168168
return count;
169169
}
170170

171-
void populateUntilAboveHighWaterMark(Vbid vbid) {
171+
int populateUntilAboveHighWaterMark(Vbid vbid) {
172172
bool populate = true;
173173
int count = 0;
174174
auto& stats = engine->getEpStats();
@@ -182,6 +182,7 @@ class STBucketQuotaTest : public STParameterizedBucketTest {
182182
populate = stats.getEstimatedTotalMemoryUsed() <=
183183
stats.mem_high_wat.load();
184184
}
185+
return count;
185186
}
186187

187188
/**
@@ -1200,6 +1201,56 @@ TEST_P(STItemPagerTest, ItemPagerEvictionOrderIsSafe) {
12001201
}
12011202
}
12021203

1204+
TEST_P(STItemPagerTest, MB43055_MemUsedDropDoesNotBreakEviction) {
1205+
// MB-43055: Test that having current memory usage drop below the low
1206+
// watermark before the item pager runs does not prevent future item
1207+
// pager runs.
1208+
1209+
if (std::get<1>(GetParam()) == "fail_new_data") {
1210+
// items are not auto-deleted, so the ItemPager does not run.
1211+
return;
1212+
}
1213+
1214+
setVBucketStateAndRunPersistTask(vbid, vbucket_state_active);
1215+
1216+
// this triggers eviction, scheduling the ItemPager task
1217+
auto itemCount = populateUntilAboveHighWaterMark(vbid);
1218+
1219+
// now delete some items to lower memory usage
1220+
for (int i = 0; i < itemCount; i++) {
1221+
auto key = makeStoredDocKey("key_" + std::to_string(i));
1222+
uint64_t cas = 0;
1223+
mutation_descr_t mutation_descr;
1224+
EXPECT_EQ(ENGINE_SUCCESS,
1225+
store->deleteItem(key,
1226+
cas,
1227+
vbid,
1228+
cookie,
1229+
{},
1230+
/*itemMeta*/ nullptr,
1231+
mutation_descr));
1232+
}
1233+
1234+
auto& stats = engine->getEpStats();
1235+
// confirm we are now below the low watermark, and can test the item pager
1236+
// behaviour
1237+
ASSERT_LT(stats.getEstimatedTotalMemoryUsed(), stats.mem_low_wat.load());
1238+
1239+
auto& lpNonioQ = *task_executor->getLpTaskQ()[NONIO_TASK_IDX];
1240+
// run the item pager. It should _not_ create and schedule a PagingVisitor
1241+
runNextTask(lpNonioQ, "Paging out items.");
1242+
1243+
EXPECT_EQ(0, lpNonioQ.getReadyQueueSize());
1244+
EXPECT_EQ(initialNonIoTasks, lpNonioQ.getFutureQueueSize());
1245+
1246+
// populate again, and confirm this time that the item pager does shedule
1247+
// a paging visitor
1248+
populateUntilAboveHighWaterMark(vbid);
1249+
1250+
runNextTask(lpNonioQ, "Paging out items.");
1251+
1252+
runNextTask(lpNonioQ, "Item pager on vb:0");
1253+
}
12031254

12041255
/**
12051256
* Test fixture for Ephemeral-only item pager tests.

0 commit comments

Comments
 (0)