Skip to content

Commit a5d4db6

Browse files
anand1976facebook-github-bot
authored andcommitted
Fix multiscan crash when fill_cache=false (facebook#13889)
Summary: When fill_cache is ReadOptions is false, multi scan Prepare crashes with the following assertion failure. In this case, CreateAndPibBlockInCache needs to directly create a block with full ownership. facebook#9 0x00007f2fc003bc93 in __GI___assert_fail (assertion=0x7f2fc2147361 "pinned_data_blocks_guard[block_idx].GetValue()", file=0x7f2fc2146e08 "table/block_based/block_based_table_iterator.cc", line=1178, function=0x7f2fc2147262 "virtual void rocksdb::BlockBasedTableIterator::Prepare(const rocksdb::MultiScanArgs *)") at assert.c:101 101 in assert.c facebook#10 0x00007f2fc1d73088 in rocksdb::BlockBasedTableIterator::Prepare(rocksdb::MultiScanArgs const*) () from /data/users/anand76/rocksdb_anand76/librocksdb.so.10.6 Pull Request resolved: facebook#13889 Test Plan: Parameterize the DBMultiScanIteratorTest tests with fill_cache Reviewed By: cbi42 Differential Revision: D80552069 Pulled By: anand1976 fbshipit-source-id: 1a0b64af1e14c63d826add1f994a832ebff12757
1 parent 0b426ff commit a5d4db6

6 files changed

Lines changed: 67 additions & 89 deletions

File tree

db/db_iterator_test.cc

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4142,13 +4142,17 @@ TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTriggerByOverwrites) {
41424142
ASSERT_EQ(1, NumTableFilesAtLevel(0));
41434143
}
41444144

4145-
class DBMultiScanIteratorTest : public DBTestBase {
4145+
class DBMultiScanIteratorTest : public DBTestBase,
4146+
public ::testing::WithParamInterface<bool> {
41464147
public:
41474148
DBMultiScanIteratorTest()
41484149
: DBTestBase("db_multi_scan_iterator_test", /*env_do_fsync=*/true) {}
41494150
};
41504151

4151-
TEST_F(DBMultiScanIteratorTest, BasicTest) {
4152+
INSTANTIATE_TEST_CASE_P(DBMultiScanIteratorTest, DBMultiScanIteratorTest,
4153+
::testing::Bool());
4154+
4155+
TEST_P(DBMultiScanIteratorTest, BasicTest) {
41524156
// Create a file
41534157
for (int i = 0; i < 100; ++i) {
41544158
std::stringstream ss;
@@ -4159,6 +4163,7 @@ TEST_F(DBMultiScanIteratorTest, BasicTest) {
41594163

41604164
std::vector<std::string> key_ranges({"k03", "k10", "k25", "k50"});
41614165
ReadOptions ro;
4166+
ro.fill_cache = GetParam();
41624167
MultiScanArgs scan_options(BytewiseComparator());
41634168
scan_options.insert(key_ranges[0], key_ranges[1]);
41644169
scan_options.insert(key_ranges[2], key_ranges[3]);
@@ -4249,7 +4254,7 @@ TEST_F(DBMultiScanIteratorTest, BasicTest) {
42494254
iter.reset();
42504255
}
42514256

4252-
TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
4257+
TEST_P(DBMultiScanIteratorTest, MixedBoundsTest) {
42534258
// Create a file
42544259
for (int i = 0; i < 100; ++i) {
42554260
std::stringstream ss;
@@ -4261,6 +4266,7 @@ TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
42614266
std::vector<std::string> key_ranges(
42624267
{"k03", "k10", "k25", "k50", "k75", "k90"});
42634268
ReadOptions ro;
4269+
ro.fill_cache = GetParam();
42644270
MultiScanArgs scan_options(BytewiseComparator());
42654271
scan_options.insert(key_ranges[0], key_ranges[1]);
42664272
scan_options.insert(key_ranges[2]);
@@ -4335,7 +4341,7 @@ TEST_F(DBMultiScanIteratorTest, MixedBoundsTest) {
43354341
iter.reset();
43364342
}
43374343

4338-
TEST_F(DBMultiScanIteratorTest, RangeAcrossFiles) {
4344+
TEST_P(DBMultiScanIteratorTest, RangeAcrossFiles) {
43394345
auto options = CurrentOptions();
43404346
options.target_file_size_base = 100 << 10; // 20KB
43414347
options.compaction_style = kCompactionStyleUniversal;
@@ -4354,6 +4360,7 @@ TEST_F(DBMultiScanIteratorTest, RangeAcrossFiles) {
43544360
ASSERT_EQ(2, NumTableFilesAtLevel(49));
43554361
std::vector<std::string> key_ranges({Key(10), Key(90)});
43564362
ReadOptions ro;
4363+
ro.fill_cache = GetParam();
43574364
MultiScanArgs scan_options(BytewiseComparator());
43584365
scan_options.insert(key_ranges[0], key_ranges[1]);
43594366
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();

table/block_based/block_based_table_iterator.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1199,7 +1199,8 @@ void BlockBasedTableIterator::Prepare(const MultiScanArgs* multiscan_opts) {
11991199
#endif
12001200
assert(pinned_data_blocks_guard[block_idx].IsEmpty());
12011201
s = table_->CreateAndPinBlockInCache<Block_kData>(
1202-
read_options_, block, &tmp_contents,
1202+
read_options_, block, table_->get_rep()->decompressor.get(),
1203+
&tmp_contents,
12031204
&(pinned_data_blocks_guard[block_idx].As<Block_kData>()));
12041205
if (!s.ok()) {
12051206
// Abort: failed to create and pin block in cache

table/block_based/block_based_table_reader.cc

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ CacheAllocationPtr CopyBufferToHeap(MemoryAllocator* allocator, Slice& buf) {
109109
CachableEntry<T>* out_parsed_block) const; \
110110
template Status BlockBasedTable::CreateAndPinBlockInCache<T>( \
111111
const ReadOptions& ro, const BlockHandle& handle, \
112-
BlockContents* block_contents, CachableEntry<T>* out_parsed_block) \
113-
const;
112+
UnownedPtr<Decompressor> decomp, BlockContents* block_contents, \
113+
CachableEntry<T>* out_parsed_block) const;
114114

115115
INSTANTIATE_BLOCKLIKE_TEMPLATES(ParsedFullFilterBlock);
116116
INSTANTIATE_BLOCKLIKE_TEMPLATES(DecompressorDict);
@@ -1741,13 +1741,55 @@ Status BlockBasedTable::LookupAndPinBlocksInCache(
17411741

17421742
template <typename TBlocklike>
17431743
Status BlockBasedTable::CreateAndPinBlockInCache(
1744-
const ReadOptions& ro, const BlockHandle& handle, BlockContents* contents,
1744+
const ReadOptions& ro, const BlockHandle& handle,
1745+
UnownedPtr<Decompressor> decomp, BlockContents* contents,
17451746
CachableEntry<TBlocklike>* out_parsed_block) const {
1746-
return MaybeReadBlockAndLoadToCache(
1747-
nullptr, ro, handle, rep_->decompressor.get(),
1748-
/*for_compaction=*/false, out_parsed_block, nullptr, nullptr, contents,
1749-
/*async_read=*/false,
1750-
/*use_block_cache_for_lookup=*/true);
1747+
CompressionType compression_type = GetBlockCompressionType(*contents);
1748+
// If we don't own the contents and we don't need to decompress, copy
1749+
// the block to heap in order to have ownership. If decompression is
1750+
// needed, then the decompressor will allocate a buffer.
1751+
if (!contents->own_bytes() && compression_type == kNoCompression) {
1752+
Slice src = Slice(contents->data.data(), BlockSizeWithTrailer(handle));
1753+
*contents = BlockContents(
1754+
CopyBufferToHeap(GetMemoryAllocator(rep_->table_options), src),
1755+
handle.size());
1756+
#ifndef NDEBUG
1757+
contents->has_trailer = true;
1758+
#endif
1759+
}
1760+
1761+
Status s;
1762+
if (ro.fill_cache) {
1763+
s = MaybeReadBlockAndLoadToCache(nullptr, ro, handle, decomp,
1764+
/*for_compaction=*/false, out_parsed_block,
1765+
nullptr, nullptr, contents,
1766+
/*async_read=*/false,
1767+
/*use_block_cache_for_lookup=*/true);
1768+
}
1769+
1770+
if (!s.ok()) {
1771+
return s;
1772+
}
1773+
1774+
// fill_cache could be false, or no block cache is configured. In that
1775+
// case, decompress if necessary and take ownership of the block
1776+
if (out_parsed_block->GetValue() == nullptr && contents != nullptr) {
1777+
BlockContents tmp_contents;
1778+
if (compression_type != kNoCompression) {
1779+
s = DecompressSerializedBlock(contents->data.data(), handle.size(),
1780+
compression_type, *decomp, &tmp_contents,
1781+
rep_->ioptions,
1782+
GetMemoryAllocator(rep_->table_options));
1783+
} else {
1784+
tmp_contents = std::move(*contents);
1785+
}
1786+
if (s.ok()) {
1787+
std::unique_ptr<TBlocklike> block_holder;
1788+
rep_->create_context.Create(&block_holder, std::move(tmp_contents));
1789+
out_parsed_block->SetOwnedValue(std::move(block_holder));
1790+
}
1791+
}
1792+
return s;
17511793
}
17521794

17531795
// If contents is nullptr, this function looks up the block caches for the

table/block_based/block_based_table_reader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ class BlockBasedTable : public TableReader {
311311
template <typename TBlocklike>
312312
Status CreateAndPinBlockInCache(
313313
const ReadOptions& ro, const BlockHandle& handle,
314-
BlockContents* block_contents,
314+
UnownedPtr<Decompressor> decomp, BlockContents* block_contents,
315315
CachableEntry<TBlocklike>* out_parsed_block) const;
316316

317317
struct Rep;

table/block_based/block_based_table_reader_sync_and_async.h

Lines changed: 2 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ DEFINE_SYNC_AND_ASYNC(void, BlockBasedTable::RetrieveMultipleBlocks)
3737
RandomAccessFileReader* file = rep_->file.get();
3838
const Footer& footer = rep_->footer;
3939
const ImmutableOptions& ioptions = rep_->ioptions;
40-
size_t read_amp_bytes_per_bit = rep_->table_options.read_amp_bytes_per_bit;
41-
MemoryAllocator* memory_allocator = GetMemoryAllocator(rep_->table_options);
4240

4341
if (ioptions.allow_mmap_reads) {
4442
size_t idx_in_batch = 0;
@@ -266,79 +264,8 @@ DEFINE_SYNC_AND_ASYNC(void, BlockBasedTable::RetrieveMultipleBlocks)
266264
}
267265

268266
if (s.ok()) {
269-
// When the blocks share the same underlying buffer (scratch or direct io
270-
// buffer), we may need to manually copy the block into heap if the
271-
// serialized block has to be inserted into a cache. That falls into the
272-
// following cases -
273-
// 1. serialized block is not compressed, it needs to be inserted into
274-
// the uncompressed block cache if there is one
275-
// 2. If the serialized block is compressed, it needs to be inserted
276-
// into the compressed block cache if there is one
277-
//
278-
// In all other cases, the serialized block is either uncompressed into a
279-
// heap buffer or there is no cache at all.
280-
CompressionType compression_type =
281-
GetBlockCompressionType(serialized_block);
282-
if ((use_fs_scratch || use_shared_buffer) &&
283-
compression_type == kNoCompression) {
284-
Slice serialized =
285-
Slice(req.result.data() + req_offset, BlockSizeWithTrailer(handle));
286-
serialized_block = BlockContents(
287-
CopyBufferToHeap(GetMemoryAllocator(rep_->table_options),
288-
serialized),
289-
handle.size());
290-
#ifndef NDEBUG
291-
serialized_block.has_trailer = true;
292-
#endif
293-
}
294-
}
295-
296-
if (s.ok()) {
297-
if (options.fill_cache) {
298-
CachableEntry<Block_kData>* block_entry = &results[idx_in_batch];
299-
// MaybeReadBlockAndLoadToCache will insert into the block caches if
300-
// necessary. Since we're passing the serialized block contents, it
301-
// will avoid looking up the block cache
302-
s = MaybeReadBlockAndLoadToCache(
303-
nullptr, options, handle, decomp,
304-
/*for_compaction=*/false, block_entry, mget_iter->get_context,
305-
/*lookup_context=*/nullptr, &serialized_block,
306-
/*async_read=*/false, /*use_block_cache_for_lookup=*/true);
307-
308-
if (!s.ok()) {
309-
statuses[idx_in_batch] = s;
310-
continue;
311-
}
312-
// block_entry value could be null if no block cache is present, i.e
313-
// BlockBasedTableOptions::no_block_cache is true and no compressed
314-
// block cache is configured. In that case, fall
315-
// through and set up the block explicitly
316-
if (block_entry->GetValue() != nullptr) {
317-
continue;
318-
}
319-
}
320-
321-
CompressionType compression_type =
322-
GetBlockCompressionType(serialized_block);
323-
BlockContents contents;
324-
if (compression_type != kNoCompression) {
325-
s = DecompressSerializedBlock(
326-
req.result.data() + req_offset, handle.size(), compression_type,
327-
*decomp, &contents, rep_->ioptions, memory_allocator);
328-
} else {
329-
// There are two cases here:
330-
// 1) caller uses the shared buffer (scratch or direct io buffer);
331-
// 2) we use the requst buffer.
332-
// If scratch buffer or direct io buffer is used, we ensure that
333-
// all serialized blocks are copyed to the heap as single blocks. If
334-
// scratch buffer is not used, we also have no combined read, so the
335-
// serialized block can be used directly.
336-
contents = std::move(serialized_block);
337-
}
338-
if (s.ok()) {
339-
results[idx_in_batch].SetOwnedValue(std::make_unique<Block_kData>(
340-
std::move(contents), read_amp_bytes_per_bit, ioptions.stats));
341-
}
267+
s = CreateAndPinBlockInCache(options, handle, decomp, &serialized_block,
268+
&results[idx_in_batch]);
342269
}
343270
statuses[idx_in_batch] = s;
344271
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a crash in iterator Prepare() when fill_cache=false

0 commit comments

Comments
 (0)