Skip to content

Commit 9444e64

Browse files
owend74trondn
authored andcommitted
MB-31495: Fix bug in getRandomKey
This patch fixes a bug where getRandomKey would loop indefinitely if start == 0. The bug is that we check for curr == start before checking if curr == max. See the original code below. start = 0; curr = start; VBucketPtr vb = getVBucket(Vbid(curr++)); while (!vb || vb->getState() != vbucket_state_active) { if (curr == start) { return GetValue(NULL, ENGINE_KEY_ENOENT); } if (curr == max) { curr = 0; } vb = getVBucket(Vbid(curr++)); } Assume the bucket is empty. The first time we enter the while loop curr == 1. curr != start and curr != max and so we will call getVBucket and increment curr to 2. We repeat until curr == 1024. On going round the while loop again curr != start, but curr == max and so set curr = 0. We then call getVbucket and increment curr to 1. On going round the while loop again curr != start (as it is 1), and hence we loop indefinitely. Change-Id: I0de040627c481fb53e0faeddd34f44f31055c241 Reviewed-on: http://review.couchbase.org/100280 Well-Formed: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]> Reviewed-by: Trond Norbye <[email protected]> Tested-by: Build Bot <[email protected]>
1 parent 69082c1 commit 9444e64

File tree

4 files changed

+42
-5
lines changed

4 files changed

+42
-5
lines changed

engines/ep/src/kv_bucket.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,24 +1395,24 @@ GetValue KVBucket::getInternal(const DocKey& key,
13951395
GetValue KVBucket::getRandomKey() {
13961396
VBucketMap::id_type max = vbMap.getSize();
13971397

1398-
const long start = random() % max;
1398+
const long start = getRandom() % max;
13991399
long curr = start;
14001400
std::unique_ptr<Item> itm;
14011401

14021402
while (itm == NULL) {
14031403
VBucketPtr vb = getVBucket(curr++);
14041404
while (!vb || vb->getState() != vbucket_state_active) {
1405-
if (curr == start) {
1406-
return GetValue(NULL, ENGINE_KEY_ENOENT);
1407-
}
14081405
if (curr == max) {
14091406
curr = 0;
14101407
}
1408+
if (curr == start) {
1409+
return GetValue(NULL, ENGINE_KEY_ENOENT);
1410+
}
14111411

14121412
vb = getVBucket(curr++);
14131413
}
14141414

1415-
if ((itm = vb->ht.getRandomKey(random()))) {
1415+
if ((itm = vb->ht.getRandomKey(getRandom()))) {
14161416
GetValue rv(std::move(itm), ENGINE_SUCCESS);
14171417
return rv;
14181418
}

engines/ep/src/kv_bucket.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ class KVBucket : public KVBucketIface {
176176
options);
177177
}
178178

179+
/**
180+
* Retrieve a value randomly from the store.
181+
*
182+
* @return a GetValue representing the value retrieved
183+
*/
179184
GetValue getRandomKey(void);
180185

181186
/**
@@ -1017,6 +1022,12 @@ class KVBucket : public KVBucketIface {
10171022

10181023
std::atomic<size_t> maxTtl;
10191024

1025+
/**
1026+
* Allows us to override the random function. This is used for testing
1027+
* purposes where we want a constant number as opposed to a random one.
1028+
*/
1029+
std::function<long()> getRandom = random;
1030+
10201031
friend class KVBucketTest;
10211032

10221033
DISALLOW_COPY_AND_ASSIGN(KVBucket);

engines/ep/tests/module_tests/kv_bucket_test.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ protocol_binary_response_status KVBucketTest::getAddResponseStatus(
334334
protocol_binary_response_status KVBucketTest::addResponseStatus =
335335
PROTOCOL_BINARY_RESPONSE_SUCCESS;
336336

337+
void KVBucketTest::setRandomFunction(std::function<long()>& randFunction) {
338+
store->getRandom = randFunction;
339+
}
337340

338341
// getKeyStats tests //////////////////////////////////////////////////////////
339342

@@ -1232,6 +1235,23 @@ TEST_P(KVBucketParamTest, AccessScannerInvalidLogLocation) {
12321235
"not exist";
12331236
}
12341237

1238+
// Check that getRandomKey works correctly when given a random value of zero
1239+
TEST_P(KVBucketParamTest, MB31495_GetRandomKey) {
1240+
std::function<long()> returnZero = []() { return 0; };
1241+
setRandomFunction(returnZero);
1242+
1243+
// Try with am empty hash table
1244+
auto gv = store->getRandomKey();
1245+
EXPECT_EQ(ENGINE_KEY_ENOENT, gv.getStatus());
1246+
1247+
Item item = store_item(
1248+
vbid, {"key", DocNamespace::DefaultCollection}, "value", 0);
1249+
1250+
// Try with a non-empty hash table
1251+
gv = store->getRandomKey();
1252+
EXPECT_EQ(ENGINE_SUCCESS, gv.getStatus());
1253+
}
1254+
12351255
class StoreIfTest : public KVBucketTest {
12361256
public:
12371257
void SetUp() override {

engines/ep/tests/module_tests/kv_bucket_test.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ class KVBucketTest : public ::testing::Test {
209209
completeWarmup = value;
210210
}
211211

212+
/**
213+
* set the random function used by KVBucket.
214+
* @param randFunction The random function to be used by the KVBucket.
215+
*/
216+
void setRandomFunction(std::function<long()>& randFunction);
217+
212218
private:
213219
/**
214220
* Initialise test objects - e.g. engine/store/cookie

0 commit comments

Comments
 (0)