Skip to content

Commit d589f50

Browse files
committed
MB-41867: [BP] Fix infinite loop due to HdrHistogram being reset
Backport of commits: b888807, b70c733, 8ee8c85 and fb7e114. Currently if a HdrHistogram has its reset() method called by one thread (T1) and is being read by another (T2) using an HdrHistoram::Iterator. There is the race that could cause the HdrHistoram::Iterator() to enter an infinite loop. This occurs as HdrHistogram's getNextValue() methods use HdrHistogam_c's hdr_iter_next() which intern uses the following function: static bool has_next(struct hdr_iter* iter) { return iter->cumulative_count < iter->total_count; } Which logically checks that the total count the Iterator was initialize with is > than the sum of all the counts its read from the buckets in the histogram. The trouble is, that the reset() method changes the bucket counts back to 0. Thus, meaning the condition cumulative_count >= total_count may never be met. To fix this wrap the std::unique_ptr<struct hdr_histogram> with a folly::Synchronized<>. This allows us to take an exclusive lock on the pointer when performing a reset() of the histogram and allows us to ensure that no iterator is reading from the histogram, as the iterator takes a non exclusive read lock for the lifetime of the HdrHistogram::Iterator struct. This also ensures that HdrHistogram::resize() is thread safe as we now take a write lock. Change-Id: I088df37c02e35303c79c63b4da410fad0f455e9b Reviewed-on: http://review.couchbase.org/c/kv_engine/+/137979 Well-Formed: Build Bot <[email protected]> Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 5958386 commit d589f50

File tree

4 files changed

+243
-132
lines changed

4 files changed

+243
-132
lines changed

engines/ep/src/item_eviction.cc

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,5 @@ uint8_t ItemEviction::convertFreqCountToNRUValue(uint8_t probCounter) {
6969
}
7070

7171
void ItemEviction::copyFreqHistogram(HdrHistogram& hist) {
72-
HdrHistogram::Iterator iter{
73-
freqHistogram.makeLinearIterator(valueUnitsPerBucket)};
74-
while (auto result = freqHistogram.getNextValueAndCount(iter)) {
75-
hist.addValueAndCount(result->first, result->second);
76-
}
72+
hist = freqHistogram;
7773
}

engines/ep/tests/module_tests/hdrhistogram_test.cc

Lines changed: 118 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,22 @@
1919

2020
#include <boost/optional.hpp>
2121
#include <folly/portability/GTest.h>
22-
#include <hdr_histogram.h>
23-
2422
#include <cmath>
25-
#include <iomanip>
2623
#include <memory>
2724
#include <random>
2825
#include <thread>
2926
#include <utility>
3027

28+
static std::vector<std::pair<uint64_t, uint64_t>> getValuesOnePerBucket(
29+
HdrHistogram& histo) {
30+
std::vector<std::pair<uint64_t, uint64_t>> values;
31+
auto iter = histo.makeLinearIterator(/* valueUnitsPerBucket */ 1);
32+
while (auto pair = histo.getNextValueAndCount(iter)) {
33+
values.push_back(*pair);
34+
}
35+
return values;
36+
}
37+
3138
/*
3239
* Unit tests for the HdrHistogram
3340
*/
@@ -79,13 +86,11 @@ TEST(HdrHistogramTest, linearIteratorTest) {
7986
histogram.addValue(ii);
8087
}
8188

82-
// Need to create the iterator after we have added the data
83-
HdrHistogram::Iterator iter{
84-
histogram.makeLinearIterator(/* valueUnitsPerBucket */ 1)};
8589
uint64_t valueCount = 0;
86-
while (auto result = histogram.getNextValueAndCount(iter)) {
87-
EXPECT_EQ(valueCount, result->first);
88-
++valueCount;
90+
auto values = getValuesOnePerBucket(histogram);
91+
EXPECT_EQ(256, values.size());
92+
for (auto& result : values) {
93+
EXPECT_EQ(valueCount++, result.first);
8994
}
9095
}
9196

@@ -162,11 +167,12 @@ TEST(HdrHistogramTest, addValueAndCountTest) {
162167
HdrHistogram histogram{0, 255, 3};
163168

164169
histogram.addValueAndCount(0, 100);
165-
// Need to create the iterator after we have added the data
166-
HdrHistogram::Iterator iter{histogram.makeLinearIterator(1)};
167-
while (auto result = histogram.getNextValueAndCount(iter)) {
168-
EXPECT_EQ(0, result->first);
169-
EXPECT_EQ(100, result->second);
170+
171+
auto values = getValuesOnePerBucket(histogram);
172+
EXPECT_EQ(1, values.size());
173+
for (auto& result : values) {
174+
EXPECT_EQ(0, result.first);
175+
EXPECT_EQ(100, result.second);
170176
}
171177
}
172178

@@ -287,14 +293,12 @@ TEST(HdrHistogramTest, addValueParallel) {
287293
ASSERT_EQ(maxVal - 1, histogram.getMaxValue());
288294
ASSERT_EQ(0, histogram.getMinValue());
289295

290-
HdrHistogram::Iterator iter{
291-
histogram.makeLinearIterator(/* valueUnitsPerBucket */ 1)};
292296
uint64_t valueCount = 0;
293-
// Assert that the right number of values were added to the histogram
294-
while (auto result = histogram.getNextValueAndCount(iter)) {
295-
ASSERT_EQ(valueCount, result->first);
296-
ASSERT_EQ(threads.size() * numOfAddIterations, result->second);
297-
++valueCount;
297+
auto values = getValuesOnePerBucket(histogram);
298+
EXPECT_EQ(maxVal, values.size());
299+
for (auto& result : values) {
300+
ASSERT_EQ(valueCount++, result.first);
301+
ASSERT_EQ(threads.size() * numOfAddIterations, result.second);
298302
}
299303
}
300304

@@ -309,68 +313,64 @@ TEST(HdrHistogramTest, percentileWhenEmptyTest) {
309313

310314
// Test the aggregation operator method
311315
TEST(HdrHistogramTest, aggregationTest) {
312-
HdrHistogram histogramOne{0, 15, 3};
313-
HdrHistogram histogramTwo{0, 15, 3};
316+
const uint16_t numberOfValues = 15;
317+
HdrHistogram histogramOne{0, numberOfValues, 3};
318+
HdrHistogram histogramTwo{0, numberOfValues, 3};
314319

315-
for (int i = 0; i < 15; i++) {
320+
for (int i = 0; i < numberOfValues; i++) {
316321
histogramOne.addValue(i);
317322
histogramTwo.addValue(i);
318323
}
319324
// Do aggregation
320325
histogramOne += histogramTwo;
321326

322-
HdrHistogram::Iterator iterOne{
323-
histogramOne.makeLinearIterator(/* valueUnitsPerBucket */ 1)};
324-
HdrHistogram::Iterator iterTwo{
325-
histogramTwo.makeLinearIterator(/* valueUnitsPerBucket */ 1)};
327+
auto histoOneValues = getValuesOnePerBucket(histogramOne);
328+
EXPECT_EQ(numberOfValues, histoOneValues.size());
329+
330+
auto histoTwoValues = getValuesOnePerBucket(histogramTwo);
331+
EXPECT_EQ(numberOfValues, histoTwoValues.size());
332+
333+
EXPECT_NE(histoOneValues, histoTwoValues);
334+
326335
uint64_t valueCount = 0;
327-
for (int i = 0; i < 15; i++) {
328-
auto resultOne = histogramOne.getNextValueAndCount(iterOne);
329-
auto resultTwo = histogramOne.getNextValueAndCount(iterTwo);
336+
for (int i = 0; i < numberOfValues; i++) {
330337
// check values are the same for both histograms
331-
EXPECT_EQ(valueCount, resultTwo->first);
332-
EXPECT_EQ(valueCount, resultOne->first);
338+
EXPECT_EQ(valueCount, histoTwoValues[i].first);
339+
EXPECT_EQ(valueCount, histoOneValues[i].first);
333340
// check that the counts for each value is twice as much as
334341
// in a bucket in histogram one as it is in histogram two
335-
EXPECT_EQ(resultOne->second, resultTwo->second * 2);
342+
EXPECT_EQ(histoOneValues[i].second, histoTwoValues[i].second * 2);
336343
++valueCount;
337344
}
338345

339346
// Check the totals of each histogram
340-
EXPECT_EQ(30, histogramOne.getValueCount());
341-
EXPECT_EQ(15, histogramTwo.getValueCount());
347+
EXPECT_EQ(numberOfValues * 2, histogramOne.getValueCount());
348+
EXPECT_EQ(numberOfValues, histogramTwo.getValueCount());
342349
}
343350

344351
// Test the aggregation operator method
345352
TEST(HdrHistogramTest, aggregationTestEmptyLhr) {
353+
const uint16_t numberOfValues = 200;
346354
HdrHistogram histogramOne{0, 15, 3};
347-
HdrHistogram histogramTwo{0, 200, 3};
355+
HdrHistogram histogramTwo{0, numberOfValues, 3};
348356

349-
for (int i = 0; i < 200; i++) {
357+
for (int i = 0; i < numberOfValues; i++) {
350358
histogramTwo.addValue(i);
351359
}
352360
// Do aggregation
353361
histogramOne += histogramTwo;
354362

355-
HdrHistogram::Iterator iterOne{
356-
histogramOne.makeLinearIterator(/* valueUnitsPerBucket */ 1)};
357-
HdrHistogram::Iterator iterTwo{
358-
histogramTwo.makeLinearIterator(/* valueUnitsPerBucket */ 1)};
363+
auto histoOneValues = getValuesOnePerBucket(histogramOne);
364+
EXPECT_EQ(numberOfValues, histoOneValues.size());
359365

360-
// Max value of LHS should be updated too 200 thus counts should be the
361-
// same for every value in both histograms
362-
for (int i = 0; i < 200; i++) {
363-
auto resultOne = histogramOne.getNextValueAndCount(iterOne);
364-
auto resultTwo = histogramOne.getNextValueAndCount(iterTwo);
365-
// check values are the same for both histograms
366-
EXPECT_EQ(resultOne->first, resultTwo->first);
367-
// check that the counts for each value are the same
368-
EXPECT_EQ(resultOne->second, resultTwo->second);
369-
}
366+
auto histoTwoValues = getValuesOnePerBucket(histogramTwo);
367+
EXPECT_EQ(numberOfValues, histoTwoValues.size());
368+
369+
EXPECT_EQ(histoTwoValues, histoOneValues);
370370

371371
// Check the totals of each histogram
372-
EXPECT_EQ(200, histogramOne.getValueCount());
373-
EXPECT_EQ(200, histogramTwo.getValueCount());
372+
EXPECT_EQ(numberOfValues, histogramOne.getValueCount());
373+
EXPECT_EQ(numberOfValues, histogramTwo.getValueCount());
374374
}
375375

376376
// Test the aggregation operator method
@@ -384,18 +384,73 @@ TEST(HdrHistogramTest, aggregationTestEmptyRhs) {
384384
// Do aggregation
385385
histogramOne += histogramTwo;
386386

387-
HdrHistogram::Iterator iter{
388-
histogramOne.makeLinearIterator(/* valueUnitsPerBucket */ 1)};
389-
390-
uint64_t valueCount = 0;
391387
// make sure the histogram has expanded in size for all 200 values
392-
while (auto result = histogramOne.getNextValueAndCount(iter)) {
393-
EXPECT_EQ(valueCount, result->first);
394-
EXPECT_EQ(1, result->second);
395-
++valueCount;
388+
auto values = getValuesOnePerBucket(histogramOne);
389+
EXPECT_EQ(200, values.size());
390+
uint64_t valueCount = 0;
391+
for (auto& result : values) {
392+
EXPECT_EQ(valueCount++, result.first);
393+
EXPECT_EQ(1, result.second);
396394
}
397395

398396
// Check the totals of each histogram
399397
EXPECT_EQ(200, histogramOne.getValueCount());
400398
EXPECT_EQ(0, histogramTwo.getValueCount());
401-
}
399+
}
400+
401+
static std::vector<boost::optional<std::pair<uint64_t, double>>> getAllValues(
402+
const HdrHistogram& histo, HdrHistogram::Iterator& iter) {
403+
std::vector<boost::optional<std::pair<uint64_t, double>>> values;
404+
while (auto pair = histo.getNextValueAndPercentile(iter)) {
405+
if (!pair.is_initialized())
406+
break;
407+
values.push_back(pair);
408+
}
409+
return values;
410+
}
411+
412+
void resetThread(HdrHistogram& histo, ThreadGate& tg) {
413+
EXPECT_EQ(histo.getValueCount(), 10);
414+
tg.threadUp();
415+
histo.reset();
416+
EXPECT_EQ(histo.getValueCount(), 0);
417+
}
418+
419+
/**
420+
* Test to check that if you create an iterator on a HdrHistogram object, then
421+
* call reset() on it in another thread and use the iterator that the iterator
422+
* doesn't end up in an infinite loop.
423+
*/
424+
TEST(HdrHistogramTest, ResetItoratorInfLoop) {
425+
Hdr2sfMicroSecHistogram histogram;
426+
for (int i = 0; i < 10; i++) {
427+
histogram.addValue(i);
428+
}
429+
{
430+
auto iter = histogram.getHistogramsIterator();
431+
auto values = getAllValues(histogram, iter);
432+
EXPECT_EQ(values.size(), 20);
433+
}
434+
435+
std::thread thread;
436+
ThreadGate tg(2);
437+
{ // Scope that holds read lock for iterator
438+
auto iter2 = histogram.getHistogramsIterator();
439+
/**
440+
* Create thread this should start running resetThread() at some point
441+
* int time will be blocked at HdrHistogram::reset() till this scope is
442+
* exited and iter2 is destroyed (releasing the read lock).
443+
* We will also create a ThreadGat to ensure the reset thread is runing
444+
* and is about to try and get an exclusive lock before reading values
445+
* from the histogram.
446+
*/
447+
thread = std::thread(resetThread, std::ref(histogram), std::ref(tg));
448+
tg.threadUp();
449+
auto values = getAllValues(histogram, iter2);
450+
EXPECT_EQ(values.size(), 20);
451+
} // iter2 read lock released
452+
453+
// wait for resetThread() to complete
454+
thread.join();
455+
EXPECT_EQ(histogram.getValueCount(), 0);
456+
}

0 commit comments

Comments
 (0)