mirror of
https://github.com/facebook/rocksdb.git
synced 2024-11-30 22:41:48 +00:00
54cb9c77d9
Summary: The following are risks associated with pointer-to-pointer reinterpret_cast: * Can produce the "wrong result" (crash or memory corruption). IIRC, in theory this can happen for any up-cast or down-cast for a non-standard-layout type, though in practice would only happen for multiple inheritance cases (where the base class pointer might be "inside" the derived object). We don't use multiple inheritance a lot, but we do. * Can mask useful compiler errors upon code change, including converting between unrelated pointer types that you are expecting to be related, and converting between pointer and scalar types unintentionally. I can only think of some obscure cases where static_cast could be troublesome when it compiles as a replacement: * Going through `void*` could plausibly cause unnecessary or broken pointer arithmetic. Suppose we have `struct Derived: public Base1, public Base2`. If we have `Derived*` -> `void*` -> `Base2*` -> `Derived*` through reinterpret casts, this could plausibly work (though technical UB) assuming the `Base2*` is not dereferenced. Changing to static cast could introduce breaking pointer arithmetic. * Unnecessary (but safe) pointer arithmetic could arise in a case like `Derived*` -> `Base2*` -> `Derived*` where before the Base2 pointer might not have been dereferenced. This could potentially affect performance. With some light scripting, I tried replacing pointer-to-pointer reinterpret_casts with static_cast and kept the cases that still compile. Most occurrences of reinterpret_cast have successfully been changed (except for java/ and third-party/). 294 changed, 257 remain. A couple of related interventions included here: * Previously Cache::Handle was not actually derived from in the implementations and just used as a `void*` stand-in with reinterpret_cast. Now there is a relationship to allow static_cast. In theory, this could introduce pointer arithmetic (as described above) but is unlikely without multiple inheritance AND non-empty Cache::Handle. * Remove some unnecessary casts to void* as this is allowed to be implicit (for better or worse). Most of the remaining reinterpret_casts are for converting to/from raw bytes of objects. We could consider better idioms for these patterns in follow-up work. I wish there were a way to implement a template variant of static_cast that would only compile if no pointer arithmetic is generated, but best I can tell, this is not possible. AFAIK the best you could do is a dynamic check that the void* conversion after the static cast is unchanged. Pull Request resolved: https://github.com/facebook/rocksdb/pull/12308 Test Plan: existing tests, CI Reviewed By: ltamasi Differential Revision: D53204947 Pulled By: pdillinger fbshipit-source-id: 9de23e618263b0d5b9820f4e15966876888a16e2
712 lines
21 KiB
C++
712 lines
21 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
//
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
// Introduction of SyncPoint effectively disabled building and running this test
|
|
// in Release build.
|
|
// which is a pity, it is a good test
|
|
|
|
#include "db/db_test_util.h"
|
|
#include "db/forward_iterator.h"
|
|
#include "port/stack_trace.h"
|
|
|
|
namespace {
|
|
static bool enable_io_uring = true;
|
|
extern "C" bool RocksDbIOUringEnable() { return enable_io_uring; }
|
|
} // namespace
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
class DBTestTailingIterator : public DBTestBase,
|
|
public ::testing::WithParamInterface<bool> {
|
|
public:
|
|
DBTestTailingIterator()
|
|
: DBTestBase("db_tailing_iterator_test", /*env_do_fsync=*/true) {}
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(DBTestTailingIterator, DBTestTailingIterator,
|
|
::testing::Bool());
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorSingle) {
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
iter->SeekToFirst();
|
|
ASSERT_TRUE(!iter->Valid());
|
|
ASSERT_OK(iter->status());
|
|
|
|
// add a record and check that iter can see it
|
|
ASSERT_OK(db_->Put(WriteOptions(), "mirko", "fodor"));
|
|
iter->SeekToFirst();
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().ToString(), "mirko");
|
|
|
|
iter->Next();
|
|
ASSERT_TRUE(!iter->Valid());
|
|
ASSERT_OK(iter->status());
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorKeepAdding) {
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options = CurrentOptions();
|
|
options.env = env.get();
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
|
|
{
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(iter->status());
|
|
std::string value(1024, 'a');
|
|
|
|
const int num_records = 10000;
|
|
for (int i = 0; i < num_records; ++i) {
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "%016d", i);
|
|
|
|
Slice key(buf, 16);
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
iter->Seek(key);
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
}
|
|
}
|
|
Close();
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorSeekToNext) {
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options = CurrentOptions();
|
|
options.env = env.get();
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
{
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(iter->status());
|
|
std::unique_ptr<Iterator> itern(
|
|
db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(itern->status());
|
|
std::string value(1024, 'a');
|
|
|
|
const int num_records = 1000;
|
|
for (int i = 1; i < num_records; ++i) {
|
|
char buf1[32];
|
|
char buf2[32];
|
|
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
|
|
|
Slice key(buf1, 20);
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
if (i % 100 == 99) {
|
|
ASSERT_OK(Flush(1));
|
|
}
|
|
|
|
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
|
Slice target(buf2, 20);
|
|
iter->Seek(target);
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
if (i == 1) {
|
|
itern->SeekToFirst();
|
|
} else {
|
|
itern->Next();
|
|
}
|
|
ASSERT_TRUE(itern->Valid());
|
|
ASSERT_EQ(itern->key().compare(key), 0);
|
|
}
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
|
for (int i = 2 * num_records; i > 0; --i) {
|
|
char buf1[32];
|
|
char buf2[32];
|
|
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
|
|
|
Slice key(buf1, 20);
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
if (i % 100 == 99) {
|
|
ASSERT_OK(Flush(1));
|
|
}
|
|
|
|
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
|
Slice target(buf2, 20);
|
|
iter->Seek(target);
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
}
|
|
}
|
|
Close();
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
const uint64_t k150KB = 150 * 1024;
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options;
|
|
options.env = env.get();
|
|
options.write_buffer_size = k150KB;
|
|
options.max_write_buffer_number = 3;
|
|
options.min_write_buffer_number_to_merge = 2;
|
|
options.env = env_;
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
int num_iters, deleted_iters;
|
|
|
|
char bufe[32];
|
|
snprintf(bufe, sizeof(bufe), "00b0%016d", 0);
|
|
Slice keyu(bufe, 20);
|
|
read_options.iterate_upper_bound = &keyu;
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(iter->status());
|
|
std::unique_ptr<Iterator> itern(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(itern->status());
|
|
std::unique_ptr<Iterator> iterh(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(iterh->status());
|
|
std::string value(1024, 'a');
|
|
bool file_iters_deleted = false;
|
|
bool file_iters_renewed_null = false;
|
|
bool file_iters_renewed_copy = false;
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
|
"ForwardIterator::SeekInternal:Return", [&](void* arg) {
|
|
ForwardIterator* fiter = static_cast<ForwardIterator*>(arg);
|
|
ASSERT_TRUE(!file_iters_deleted ||
|
|
fiter->TEST_CheckDeletedIters(&deleted_iters, &num_iters));
|
|
});
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
|
"ForwardIterator::Next:Return", [&](void* arg) {
|
|
ForwardIterator* fiter = static_cast<ForwardIterator*>(arg);
|
|
ASSERT_TRUE(!file_iters_deleted ||
|
|
fiter->TEST_CheckDeletedIters(&deleted_iters, &num_iters));
|
|
});
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
|
"ForwardIterator::RenewIterators:Null",
|
|
[&](void* /*arg*/) { file_iters_renewed_null = true; });
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
|
"ForwardIterator::RenewIterators:Copy",
|
|
[&](void* /*arg*/) { file_iters_renewed_copy = true; });
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
|
const int num_records = 1000;
|
|
for (int i = 1; i < num_records; ++i) {
|
|
char buf1[32];
|
|
char buf2[32];
|
|
char buf3[32];
|
|
char buf4[32];
|
|
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
|
snprintf(buf3, sizeof(buf3), "00b0%016d", i * 5);
|
|
|
|
Slice key(buf1, 20);
|
|
ASSERT_OK(Put(1, key, value));
|
|
Slice keyn(buf3, 20);
|
|
ASSERT_OK(Put(1, keyn, value));
|
|
|
|
if (i % 100 == 99) {
|
|
ASSERT_OK(Flush(1));
|
|
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
|
if (i == 299) {
|
|
file_iters_deleted = true;
|
|
}
|
|
snprintf(buf4, sizeof(buf4), "00a0%016d", i * 5 / 2);
|
|
Slice target(buf4, 20);
|
|
iterh->Seek(target);
|
|
ASSERT_TRUE(iter->Valid());
|
|
for (int j = (i + 1) * 5 / 2; j < i * 5; j += 5) {
|
|
iterh->Next();
|
|
ASSERT_TRUE(iterh->Valid());
|
|
}
|
|
if (i == 299) {
|
|
file_iters_deleted = false;
|
|
}
|
|
}
|
|
|
|
file_iters_deleted = true;
|
|
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
|
Slice target(buf2, 20);
|
|
iter->Seek(target);
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
ASSERT_LE(num_iters, 1);
|
|
if (i == 1) {
|
|
itern->SeekToFirst();
|
|
} else {
|
|
itern->Next();
|
|
}
|
|
ASSERT_TRUE(itern->Valid());
|
|
ASSERT_EQ(itern->key().compare(key), 0);
|
|
ASSERT_LE(num_iters, 1);
|
|
file_iters_deleted = false;
|
|
}
|
|
ASSERT_TRUE(file_iters_renewed_null);
|
|
ASSERT_TRUE(file_iters_renewed_copy);
|
|
iter = nullptr;
|
|
itern = nullptr;
|
|
iterh = nullptr;
|
|
BlockBasedTableOptions table_options;
|
|
table_options.no_block_cache = true;
|
|
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
read_options.read_tier = kBlockCacheTier;
|
|
std::unique_ptr<Iterator> iteri(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(iteri->status());
|
|
char buf5[32];
|
|
snprintf(buf5, sizeof(buf5), "00a0%016d", (num_records / 2) * 5 - 2);
|
|
Slice target1(buf5, 20);
|
|
iteri->Seek(target1);
|
|
ASSERT_TRUE(iteri->status().IsIncomplete());
|
|
iteri = nullptr;
|
|
|
|
read_options.read_tier = kReadAllTier;
|
|
options.table_factory.reset(NewBlockBasedTableFactory());
|
|
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
|
iter.reset(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(iter->status());
|
|
for (int i = 2 * num_records; i > 0; --i) {
|
|
char buf1[32];
|
|
char buf2[32];
|
|
snprintf(buf1, sizeof(buf1), "00a0%016d", i * 5);
|
|
|
|
Slice key(buf1, 20);
|
|
ASSERT_OK(Put(1, key, value));
|
|
|
|
if (i % 100 == 99) {
|
|
ASSERT_OK(Flush(1));
|
|
}
|
|
|
|
snprintf(buf2, sizeof(buf2), "00a0%016d", i * 5 - 2);
|
|
Slice target(buf2, 20);
|
|
iter->Seek(target);
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().compare(key), 0);
|
|
}
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorDeletes) {
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options = CurrentOptions();
|
|
options.env = env.get();
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
{
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(iter->status());
|
|
|
|
// write a single record, read it using the iterator, then delete it
|
|
ASSERT_OK(Put(1, "0test", "test"));
|
|
iter->SeekToFirst();
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().ToString(), "0test");
|
|
ASSERT_OK(Delete(1, "0test"));
|
|
|
|
// write many more records
|
|
const int num_records = 10000;
|
|
std::string value(1024, 'A');
|
|
|
|
for (int i = 0; i < num_records; ++i) {
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "1%015d", i);
|
|
|
|
Slice key(buf, 16);
|
|
ASSERT_OK(Put(1, key, value));
|
|
}
|
|
|
|
// force a flush to make sure that no records are read from memtable
|
|
ASSERT_OK(Flush(1));
|
|
|
|
// skip "0test"
|
|
iter->Next();
|
|
|
|
// make sure we can read all new records using the existing iterator
|
|
int count = 0;
|
|
for (; iter->Valid(); iter->Next(), ++count) {
|
|
;
|
|
}
|
|
ASSERT_OK(iter->status());
|
|
ASSERT_EQ(count, num_records);
|
|
}
|
|
Close();
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorPrefixSeek) {
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options = CurrentOptions();
|
|
options.env = env.get();
|
|
options.create_if_missing = true;
|
|
options.disable_auto_compactions = true;
|
|
options.prefix_extractor.reset(NewFixedPrefixTransform(2));
|
|
options.memtable_factory.reset(NewHashSkipListRepFactory(16));
|
|
options.allow_concurrent_memtable_write = false;
|
|
DestroyAndReopen(options);
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
{
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(iter->status());
|
|
ASSERT_OK(Put(1, "0101", "test"));
|
|
|
|
ASSERT_OK(Flush(1));
|
|
|
|
ASSERT_OK(Put(1, "0202", "test"));
|
|
|
|
// Seek(0102) shouldn't find any records since 0202 has a different prefix
|
|
iter->Seek("0102");
|
|
ASSERT_TRUE(!iter->Valid());
|
|
|
|
iter->Seek("0202");
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().ToString(), "0202");
|
|
|
|
iter->Next();
|
|
ASSERT_TRUE(!iter->Valid());
|
|
ASSERT_OK(iter->status());
|
|
}
|
|
Close();
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorIncomplete) {
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options = CurrentOptions();
|
|
options.env = env.get();
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
read_options.read_tier = kBlockCacheTier;
|
|
|
|
std::string key("key");
|
|
std::string value("value");
|
|
|
|
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
|
|
|
{
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
ASSERT_OK(iter->status());
|
|
iter->SeekToFirst();
|
|
// we either see the entry or it's not in cache
|
|
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
|
|
|
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
|
iter->SeekToFirst();
|
|
// should still be true after compaction
|
|
ASSERT_TRUE(iter->Valid() || iter->status().IsIncomplete());
|
|
}
|
|
Close();
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorSeekToSame) {
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options = CurrentOptions();
|
|
options.env = env.get();
|
|
options.compaction_style = kCompactionStyleUniversal;
|
|
options.write_buffer_size = 1000;
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
const int NROWS = 10000;
|
|
// Write rows with keys 00000, 00002, 00004 etc.
|
|
for (int i = 0; i < NROWS; ++i) {
|
|
char buf[100];
|
|
snprintf(buf, sizeof(buf), "%05d", 2 * i);
|
|
std::string key(buf);
|
|
std::string value("value");
|
|
ASSERT_OK(db_->Put(WriteOptions(), key, value));
|
|
}
|
|
|
|
{
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
ASSERT_OK(iter->status());
|
|
// Seek to 00001. We expect to find 00002.
|
|
std::string start_key = "00001";
|
|
iter->Seek(start_key);
|
|
ASSERT_TRUE(iter->Valid());
|
|
|
|
std::string found = iter->key().ToString();
|
|
ASSERT_EQ("00002", found);
|
|
|
|
// Now seek to the same key. The iterator should remain in the same
|
|
// position.
|
|
iter->Seek(found);
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(found, iter->key().ToString());
|
|
}
|
|
Close();
|
|
}
|
|
|
|
// Sets iterate_upper_bound and verifies that ForwardIterator doesn't call
|
|
// Seek() on immutable iterators when target key is >= prev_key and all
|
|
// iterators, including the memtable iterator, are over the upper bound.
|
|
TEST_P(DBTestTailingIterator, TailingIteratorUpperBound) {
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options = CurrentOptions();
|
|
options.env = env.get();
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
const Slice upper_bound("20", 3);
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
read_options.iterate_upper_bound = &upper_bound;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
ASSERT_OK(Put(1, "11", "11"));
|
|
ASSERT_OK(Put(1, "12", "12"));
|
|
ASSERT_OK(Put(1, "22", "22"));
|
|
ASSERT_OK(Flush(1)); // flush all those keys to an immutable SST file
|
|
|
|
// Add another key to the memtable.
|
|
ASSERT_OK(Put(1, "21", "21"));
|
|
|
|
{
|
|
bool read_async_called = false;
|
|
|
|
SyncPoint::GetInstance()->SetCallBack(
|
|
"UpdateResults::io_uring_result",
|
|
[&](void* /*arg*/) { read_async_called = true; });
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
auto it =
|
|
std::unique_ptr<Iterator>(db_->NewIterator(read_options, handles_[1]));
|
|
ASSERT_OK(it->status());
|
|
it->Seek("12");
|
|
ASSERT_TRUE(it->Valid());
|
|
ASSERT_EQ("12", it->key().ToString());
|
|
|
|
it->Next();
|
|
// Not valid since "21" is over the upper bound.
|
|
ASSERT_FALSE(it->Valid());
|
|
ASSERT_OK(it->status());
|
|
// This keeps track of the number of times NeedToSeekImmutable() was true.
|
|
int immutable_seeks = 0;
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
|
"ForwardIterator::SeekInternal:Immutable",
|
|
[&](void* /*arg*/) { ++immutable_seeks; });
|
|
|
|
// Seek to 13. This should not require any immutable seeks.
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
|
it->Seek("13");
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
|
|
|
SyncPoint::GetInstance()->SetCallBack(
|
|
"UpdateResults::io_uring_result",
|
|
[&](void* /*arg*/) { read_async_called = true; });
|
|
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
|
|
|
ASSERT_FALSE(it->Valid());
|
|
ASSERT_OK(it->status());
|
|
if (GetParam() && read_async_called) {
|
|
ASSERT_EQ(1, immutable_seeks);
|
|
} else {
|
|
ASSERT_EQ(0, immutable_seeks);
|
|
}
|
|
}
|
|
Close();
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, TailingIteratorGap) {
|
|
// level 1: [20, 25] [35, 40]
|
|
// level 2: [10 - 15] [45 - 50]
|
|
// level 3: [20, 30, 40]
|
|
// Previously there is a bug in tailing_iterator that if there is a gap in
|
|
// lower level, the key will be skipped if it is within the range between
|
|
// the largest key of index n file and the smallest key of index n+1 file
|
|
// if both file fit in that gap. In this example, 25 < key < 35
|
|
// https://github.com/facebook/rocksdb/issues/1372
|
|
if (mem_env_ || encrypted_env_) {
|
|
ROCKSDB_GTEST_BYPASS("Test requires non-mem or non-encrypted environment");
|
|
return;
|
|
}
|
|
std::unique_ptr<Env> env(
|
|
new CompositeEnvWrapper(env_, FileSystem::Default()));
|
|
Options options = CurrentOptions();
|
|
options.env = env.get();
|
|
CreateAndReopenWithCF({"pikachu"}, options);
|
|
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
ASSERT_OK(Put(1, "20", "20"));
|
|
ASSERT_OK(Put(1, "30", "30"));
|
|
ASSERT_OK(Put(1, "40", "40"));
|
|
ASSERT_OK(Flush(1));
|
|
MoveFilesToLevel(3, 1);
|
|
|
|
ASSERT_OK(Put(1, "10", "10"));
|
|
ASSERT_OK(Put(1, "15", "15"));
|
|
ASSERT_OK(Flush(1));
|
|
ASSERT_OK(Put(1, "45", "45"));
|
|
ASSERT_OK(Put(1, "50", "50"));
|
|
ASSERT_OK(Flush(1));
|
|
MoveFilesToLevel(2, 1);
|
|
|
|
ASSERT_OK(Put(1, "20", "20"));
|
|
ASSERT_OK(Put(1, "25", "25"));
|
|
ASSERT_OK(Flush(1));
|
|
ASSERT_OK(Put(1, "35", "35"));
|
|
ASSERT_OK(Put(1, "40", "40"));
|
|
ASSERT_OK(Flush(1));
|
|
MoveFilesToLevel(1, 1);
|
|
|
|
ColumnFamilyMetaData meta;
|
|
db_->GetColumnFamilyMetaData(handles_[1], &meta);
|
|
|
|
{
|
|
std::unique_ptr<Iterator> it(db_->NewIterator(read_options, handles_[1]));
|
|
it->Seek("30");
|
|
ASSERT_TRUE(it->Valid());
|
|
ASSERT_EQ("30", it->key().ToString());
|
|
|
|
it->Next();
|
|
ASSERT_TRUE(it->Valid());
|
|
ASSERT_EQ("35", it->key().ToString());
|
|
|
|
it->Next();
|
|
ASSERT_TRUE(it->Valid());
|
|
ASSERT_EQ("40", it->key().ToString());
|
|
|
|
ASSERT_OK(it->status());
|
|
}
|
|
Close();
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, SeekWithUpperBoundBug) {
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
const Slice upper_bound("cc", 3);
|
|
read_options.iterate_upper_bound = &upper_bound;
|
|
|
|
// 1st L0 file
|
|
ASSERT_OK(db_->Put(WriteOptions(), "aa", "SEEN"));
|
|
ASSERT_OK(Flush());
|
|
|
|
// 2nd L0 file
|
|
ASSERT_OK(db_->Put(WriteOptions(), "zz", "NOT-SEEN"));
|
|
ASSERT_OK(Flush());
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
ASSERT_OK(iter->status());
|
|
|
|
iter->Seek("aa");
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().ToString(), "aa");
|
|
}
|
|
|
|
TEST_P(DBTestTailingIterator, SeekToFirstWithUpperBoundBug) {
|
|
ReadOptions read_options;
|
|
read_options.tailing = true;
|
|
if (GetParam()) {
|
|
read_options.async_io = true;
|
|
}
|
|
const Slice upper_bound("cc", 3);
|
|
read_options.iterate_upper_bound = &upper_bound;
|
|
|
|
// 1st L0 file
|
|
ASSERT_OK(db_->Put(WriteOptions(), "aa", "SEEN"));
|
|
ASSERT_OK(Flush());
|
|
|
|
// 2nd L0 file
|
|
ASSERT_OK(db_->Put(WriteOptions(), "zz", "NOT-SEEN"));
|
|
ASSERT_OK(Flush());
|
|
|
|
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
|
ASSERT_OK(iter->status());
|
|
|
|
iter->SeekToFirst();
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().ToString(), "aa");
|
|
|
|
iter->Next();
|
|
ASSERT_FALSE(iter->Valid());
|
|
|
|
iter->SeekToFirst();
|
|
ASSERT_TRUE(iter->Valid());
|
|
ASSERT_EQ(iter->key().ToString(), "aa");
|
|
}
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|