diff --git a/file/prefetch_test.cc b/file/prefetch_test.cc index 93d471ff4706..9afa344ebfe6 100644 --- a/file/prefetch_test.cc +++ b/file/prefetch_test.cc @@ -1675,6 +1675,62 @@ TEST_P(PrefetchTrimReadaheadTestParam, PrefixSameAsStart) { Close(); } +TEST_P(PrefetchTrimReadaheadTestParam, IterateUpperBoundAtEndOfIndex) { + if (mem_env_ || encrypted_env_) { + ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment"); + return; + } + const bool auto_readahead_size = std::get<1>(GetParam()); + + std::shared_ptr fs = std::make_shared( + FileSystem::Default(), false /* support_prefetch */, + true /* small_buffer_alignment */); + std::unique_ptr env(new CompositeEnvWrapper(env_, fs)); + Options options; + SetGenericOptions(env.get(), options); + options.prefix_extractor.reset(); + BlockBasedTableOptions table_options; + SetBlockBasedTableOptions(table_options); + options.table_factory.reset(NewBlockBasedTableFactory(table_options)); + + ASSERT_OK(TryReopen(options)); + + for (int i = 0; i < 64; ++i) { + ASSERT_OK(db_->Put(WriteOptions(), "key" + std::to_string(i), + rnd.RandomString(100))); + } + ASSERT_OK(db_->Flush(FlushOptions())); + + ReadOptions ro; + ro.async_io = true; + ro.auto_readahead_size = auto_readahead_size; + ro.readahead_size = 1024 * 1024; + const Slice upper_bound("keyz"); + ro.iterate_upper_bound = &upper_bound; + + ASSERT_OK(options.statistics->Reset()); + int num_keys = 0; + Status iter_status; + { + auto iter = std::unique_ptr(db_->NewIterator(ro)); + for (iter->Seek("key0"); iter->Valid(); iter->Next()) { + ++num_keys; + } + iter_status = iter->status(); + } + auto readahead_trimmed = + options.statistics->getTickerCount(READAHEAD_TRIMMED); + + Close(); + ASSERT_OK(iter_status); + ASSERT_EQ(num_keys, 64); + if (auto_readahead_size) { + ASSERT_GT(readahead_trimmed, 0); + } else { + ASSERT_EQ(readahead_trimmed, 0); + } +} + // This test verifies the functionality of ReadOptions.adaptive_readahead. TEST_P(PrefetchTest, DBIterLevelReadAhead) { const int kNumKeys = 1000; diff --git a/table/block_based/block_based_table_iterator.cc b/table/block_based/block_based_table_iterator.cc index fc21f3634d13..8818fd962d02 100644 --- a/table/block_based/block_based_table_iterator.cc +++ b/table/block_based/block_based_table_iterator.cc @@ -873,6 +873,14 @@ void BlockBasedTableIterator::BlockCacheLookupForReadAheadSize( return; } + // Readahead lookup may advance index_iter_ to the end of the file while the + // current block is still being consumed. In that case there is no next block + // boundary to inspect, so skip further tuning instead of dereferencing an + // exhausted index iterator. + if (!index_iter_->Valid()) { + return; + } + size_t footer = table_->get_rep()->footer.GetBlockTrailerSize(); if (read_curr_block && !DoesContainBlockHandles() && IsNextBlockOutOfReadaheadBound()) { diff --git a/table/block_based/block_based_table_iterator.h b/table/block_based/block_based_table_iterator.h index 528553a7a9cb..eb70718c2d55 100644 --- a/table/block_based/block_based_table_iterator.h +++ b/table/block_based/block_based_table_iterator.h @@ -468,6 +468,7 @@ class BlockBasedTableIterator : public InternalIteratorBase { } bool IsNextBlockOutOfReadaheadBound() { + assert(index_iter_->Valid()); const Slice& index_iter_user_key = index_iter_->user_key(); // If curr block's index key >= iterate_upper_bound, it means all the keys // in next block or above are out of bound.