Skip to content

Commit e84b5bd

Browse files
mikewiacekcmumford
authored andcommitted
This CL fixes a bug encountered when reading records from leveldb files that have been split, as in a [] input task split.
Detailed description: Suppose an input split is generated between two leveldb record blocks and the preceding block ends with null padding. A reader that previously read at least 1 record within the first block (before encountering the padding) upon trying to read the next record, will successfully and correctly read the next logical record from the subsequent block, but will return a last record offset pointing to the padding in the first block. When this happened in a [], it resulted in duplicate records being handled at what appeared to be different offsets that were separated by only a few bytes. This behavior is only observed when at least 1 record was read from the first block before encountering the padding. If the initial offset for a reader was within the padding, the correct record offset would be reported, namely the offset within the second block. The tests failed to catch this scenario/bug, because each read test only read a single record with an initial offset. This CL adds an explicit test case for this scenario, and modifies the test structure to read all remaining records in the test case after an initial offset is specified. Thus an initial offset that jumps to record #3, with 5 total records in the test file, will result in reading 2 records, and validating the offset of each of them in order to pass successfully. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=115338487
1 parent 3211343 commit e84b5bd

File tree

2 files changed

+40
-12
lines changed

2 files changed

+40
-12
lines changed

db/log_reader.cc

+7-1
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,14 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch) {
7373

7474
Slice fragment;
7575
while (true) {
76-
uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size();
7776
const unsigned int record_type = ReadPhysicalRecord(&fragment);
77+
78+
// ReadPhysicalRecord may have only had an empty trailer remaining in its
79+
// internal buffer. Calculate the offset of the next physical record now
80+
// that it has returned, properly accounting for its header size.
81+
uint64_t physical_record_offset =
82+
end_of_buffer_offset_ - buffer_.size() - kHeaderSize - fragment.size();
83+
7884
if (resyncing_) {
7985
if (record_type == kMiddleType) {
8086
continue;

db/log_test.cc

+33-11
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class LogTest {
110110
// Record metadata for testing initial offset functionality
111111
static size_t initial_offset_record_sizes_[];
112112
static uint64_t initial_offset_last_record_offsets_[];
113+
static int num_initial_offset_records_;
113114

114115
public:
115116
LogTest() : reading_(false),
@@ -192,7 +193,7 @@ class LogTest {
192193
}
193194

194195
void WriteInitialOffsetLog() {
195-
for (int i = 0; i < 4; i++) {
196+
for (int i = 0; i < num_initial_offset_records_; i++) {
196197
std::string record(initial_offset_record_sizes_[i],
197198
static_cast<char>('a' + i));
198199
Write(record);
@@ -223,14 +224,20 @@ class LogTest {
223224
source_.contents_ = Slice(dest_.contents_);
224225
Reader* offset_reader = new Reader(&source_, &report_, true/*checksum*/,
225226
initial_offset);
226-
Slice record;
227-
std::string scratch;
228-
ASSERT_TRUE(offset_reader->ReadRecord(&record, &scratch));
229-
ASSERT_EQ(initial_offset_record_sizes_[expected_record_offset],
230-
record.size());
231-
ASSERT_EQ(initial_offset_last_record_offsets_[expected_record_offset],
232-
offset_reader->LastRecordOffset());
233-
ASSERT_EQ((char)('a' + expected_record_offset), record.data()[0]);
227+
228+
// Read all records from expected_record_offset through the last one.
229+
ASSERT_LT(expected_record_offset, num_initial_offset_records_);
230+
for (; expected_record_offset < num_initial_offset_records_;
231+
++expected_record_offset) {
232+
Slice record;
233+
std::string scratch;
234+
ASSERT_TRUE(offset_reader->ReadRecord(&record, &scratch));
235+
ASSERT_EQ(initial_offset_record_sizes_[expected_record_offset],
236+
record.size());
237+
ASSERT_EQ(initial_offset_last_record_offsets_[expected_record_offset],
238+
offset_reader->LastRecordOffset());
239+
ASSERT_EQ((char)('a' + expected_record_offset), record.data()[0]);
240+
}
234241
delete offset_reader;
235242
}
236243
};
@@ -239,15 +246,26 @@ size_t LogTest::initial_offset_record_sizes_[] =
239246
{10000, // Two sizable records in first block
240247
10000,
241248
2 * log::kBlockSize - 1000, // Span three blocks
242-
1};
249+
1,
250+
13716, // Consume all but two bytes of block 3.
251+
log::kBlockSize - kHeaderSize, // Consume the entirety of block 4.
252+
};
243253

244254
uint64_t LogTest::initial_offset_last_record_offsets_[] =
245255
{0,
246256
kHeaderSize + 10000,
247257
2 * (kHeaderSize + 10000),
248258
2 * (kHeaderSize + 10000) +
249-
(2 * log::kBlockSize - 1000) + 3 * kHeaderSize};
259+
(2 * log::kBlockSize - 1000) + 3 * kHeaderSize,
260+
2 * (kHeaderSize + 10000) +
261+
(2 * log::kBlockSize - 1000) + 3 * kHeaderSize
262+
+ kHeaderSize + 1,
263+
3 * log::kBlockSize,
264+
};
250265

266+
// LogTest::initial_offset_last_record_offsets_ must be defined before this.
267+
int LogTest::num_initial_offset_records_ =
268+
sizeof(LogTest::initial_offset_last_record_offsets_)/sizeof(uint64_t);
251269

252270
TEST(LogTest, Empty) {
253271
ASSERT_EQ("EOF", Read());
@@ -553,6 +571,10 @@ TEST(LogTest, ReadFourthStart) {
553571
3);
554572
}
555573

574+
TEST(LogTest, ReadInitialOffsetIntoBlockPadding) {
575+
CheckInitialOffsetRecord(3 * log::kBlockSize - 3, 5);
576+
}
577+
556578
TEST(LogTest, ReadEnd) {
557579
CheckOffsetPastEndReturnsNoRecords(0);
558580
}

0 commit comments

Comments
 (0)