Compare commits

...
Author SHA1 Message Date
Richard Barnes fa616f0e79 Fix string-conversion issue in internal_repo_rocksdb/repo/utilities/persistent_cache/volatile_tier_impl.cc +5 (#14296)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14296

This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(!"this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Reviewed By: anand1976

Differential Revision: D92013593

fbshipit-source-id: 0b4e00339bef3f76fc5b9ad35e2383c5e4f828f9
2026-02-25 10:30:50 -08:00
Richard Barnes 96db15c6e6 Fix string-conversion issue in internal_repo_rocksdb/repo/table/block_based/index_builder.cc +1
Summary:
This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(false && "this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Differential Revision: D92886041

fbshipit-source-id: 6adfaa102f12e293491cc579ec92b48834d1d0a8
2026-02-25 10:30:50 -08:00
Richard Barnes 912596031a Fix string-conversion issue in internal_repo_rocksdb/repo/utilities/persistent_cache/block_cache_tier_file.cc +2 (#14312)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14312

This could is triggering `-Wstring-conversion`, which presents as:
```
warning: implicit conversion turns string literal into bool: A to B
```
This is often a bug and what was intended. The most frequent cause is the code was:
```
void foo(bool) { ... }
void foo(std::string) { ... }
foo("this gets interpreted as a bool");
```

It is also possible the issue is innocuous as part of an assert:
```
assert(!"this string is true, so the assertion is false");
EXPECT_FALSE("this string is true, so the expect fails");
```
in these cases the use is to "cute", so we modify the code to make it more obvious.
```
assert(false && "the compiler recognizes and doesn't complain about this pattern");
FAIL() << "much more obvious";
```

Reviewed By: dmm-fb

Differential Revision: D92528316

fbshipit-source-id: 93fbb624e8731c4cdb559746b44c1aa71d786304
2026-02-25 10:30:50 -08:00
Andrew Chang f4fe40845a Update HISTORY and version to 10.11.2 2026-02-25 10:30:29 -08:00
Andrew Chang 40a5f27f6e Check io_uring probe result in SupportedOps (#14355)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14355

SupportedOps advertised kAsyncIO based only on the IsIOUringEnabled() weak symbol check, without verifying that the constructor's io_uring probe actually succeeded. Add a thread_local_async_read_io_urings_ null check so kAsyncIO is only reported when the probe passed. Also update the constructor to probe with the same IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN flags that ReadAsync and MultiRead use at runtime.

Reviewed By: anand1976

Differential Revision: D93780065

fbshipit-source-id: 6f51f544b267cb39d09b49949a9485f55eeae12e
2026-02-24 16:36:14 -08:00
Andrew Chang 9676ce2f84 Propagate file_checksum through FileOptions on NewRandomAccessFile (#14321)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14321

Add file_checksum and file_checksum_func_name fields to FileOptions so that downstream FileSystem implementations can access per-file checksum metadata when SST files are opened. The fields are populated from FileMetaData at all call sites where SST files are opened via NewRandomAccessFile: TableCache::GetTableReader, Version::GetTableProperties, and CompactionJob::ReadTablePropertiesDirectly. Also fixes the fallback path in TableCache::GetTableReader to use the local fopts (with temperature and checksum) instead of the original file_options.

Added a kNoFileChecksumFuncName which is distinct from  kUnknownFileChecksumFuncName:

 - kUnknownFileChecksumFuncName ("Unknown"): We have FileMetaData for this file, and the metadata says no checksum was computed (no factory was configured when the file was written). This is a property of the file itself.
- kNoFileChecksumFuncName ("Unavailable"): We don't even have FileMetaData — we're opening this file in a context where there's no checksum metadata to propagate at all (e.g., SstFileDumper, SstFileReader, checksum generation). It's a property of the call site, not the file.

So the assertion file_checksum.empty() is correct for both, but for different reasons — one says "the file has no checksum," the other says "we have no idea about this file's checksum."

Reviewed By: pdillinger

Differential Revision: D92728944

fbshipit-source-id: 8fd34ea22ca87090b26d0a55c921f354f97f1ffc
2026-02-24 16:36:06 -08:00
Peter Dillinger e506aa7154 Fix format compatibility issues, extend test (#14323)
Summary:
See https://github.com/facebook/rocksdb/issues/14240 which brought this to my attention. Here I've added range deletions and compactions to the format compatible test, and fixed or worked-around compatibility issues (likely longstanding).

The first fix was in Version::MaybeInitializeFileMetaData for an assertion failure simply from adding range deletions from some 5.x version.

The second fix is a broader work-around for older SST files with unreliable num_entries/num_range_deletions/num_deletions statistics in their table properties. We depend on them only for some paranoid checks for compaction, so in my assessment the best way to deal with those files is to exclude the paranoid checks when dealing with the files with unrelaible data. (Details in code comments.) The important part is that compacting old files is exceptionally rare, so we aren't really interefering with the paranoid checks doing thier job on an ongoing basis.

This depends on https://github.com/facebook/rocksdb/issues/14315 (just landed) because there is a remaining undiagnosed problem with some very early releases, but I'm not fixing that because its support is being dropped.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14323

Test Plan: test extended (ran locally excluding some releases)

Reviewed By: xingbowang

Differential Revision: D93032653

Pulled By: pdillinger

fbshipit-source-id: f90b32f30ba4764692e68d23705f42c778e0dc1d
2026-02-16 11:13:06 -08:00
Anand Ananthabhotla 6d4c2c3297 Fix racy assertion in AbortIOPartialHandlesBug test (#14319)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14319

The test asserted h1->is_finished and h2->is_finished immediately after AbortIO({H0}), before calling Poll. This is invalid because AbortIO only guarantees that handles in its abort set are finalized. Non-aborted handles' CQEs may or may not be consumed during AbortIO depending on io_uring completion ordering. If H0's two CQEs (original read + cancel) arrive before H1/H2's CQEs, AbortIO breaks out of its wait loop without processing them. Move the H1/H2 is_finished assertions to after Poll, which correctly handles either case. Also remove the racy req_count checks for non-aborted handles since Poll does not increment req_count.

Reviewed By: jaykorean

Differential Revision: D92848827

fbshipit-source-id: 0c09b44ceada99877e8311cff799fa94f1056545
2026-02-16 11:13:06 -08:00
Anand Ananthabhotla 2ceb3ef4d2 Add a cleanup target to crash_test.mk (#14286)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14286

Add the db_c leanup target which can be used by CI test scripts to delete the db on failure. The db_crashtest.py doesn't automatically delete on error.

Reviewed By: jaykorean

Differential Revision: D91912877

fbshipit-source-id: d36ec0896fba64faaafe055d8673e437e85d0c3a
2026-02-06 12:20:35 -08:00
anand76 edc76202a2 Update HISTORY and version to 10.11.1 2026-02-06 12:20:35 -08:00
Andrew Chang b44c70adcf Fix crash in GetLiveFilesStorageInfo on read-only DB (#14306)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14306

GetLiveFilesStorageInfo crashes when called on a read-only RocksDB because
it calls FlushWAL(), which accesses logs_.back() on an empty deque.

Root cause: DBImplReadOnly overrides SyncWAL() to return NotSupported, but
does NOT override FlushWAL(). Read-only DBs have an empty logs_ deque
because they don't create WAL writers during recovery - there's nothing to
write, so no WAL infrastructure is initialized.

The reason SyncWAL was originally marked NotSupported is that these WAL
operations (SyncWAL syncs buffer to disk, FlushWAL flushes to OS buffer)
require an active WAL writer at logs_.back().writer. Since read-only DBs:
1. Cannot perform writes
2. Don't create WAL files for writing
3. Have an empty logs_ deque

...there's no WAL writer to sync or flush. The operations are semantically
meaningless, not just "forbidden write operations."

The fix adds a FlushWAL override matching the SyncWAL pattern. The caller
in db_filesnapshot.cc:403-405 already handles IsNotSupported() gracefully:
  if (s.IsNotSupported()) { s = Status::OK(); }

Reviewed By: pdillinger

Differential Revision: D92419557

fbshipit-source-id: 7079071209b3c7be41a2c98c9b691e68bc031595
2026-02-06 12:20:35 -08:00
Andrew Chang 71eb5a1646 Propagate Poll errors in FilePrefetchBuffer::PollIfNeeded (#14282)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14282

Previously, `PollIfNeeded` returned `void` and silently ignored errors from
`fs_->Poll()` by calling `PermitUncheckedError()`. This could lead to silent
data corruption or unexpected behavior when Poll operations fail.

This diff changes `PollIfNeeded` to return `Status` and properly propagate
Poll errors to callers. When Poll fails:
1. The IO handle is cleaned up via `DestroyAndClearIOHandle`
2. The error status is returned to the caller
3. Callers (`HandleOverlappingAsyncData` and `PrefetchInternal`) now check
   and propagate this error

Also adds a `TEST_SYNC_POINT_CALLBACK` to allow tests to inject Poll errors.

Reviewed By: anand1976

Differential Revision: D91624185

fbshipit-source-id: 8dd0ee6588ed1ce4bf080bcf857b778c5140ccf5
2026-02-06 12:20:35 -08:00
Anand Ananthabhotla 63a256d768 Fix AbortIO consuming completions for non-aborted handles (#14301)
Summary:
Pull Request resolved: https://github.com/facebook/rocksdb/pull/14301

When AbortIO was called with a subset of outstanding async read handles,
it would consume io_uring completions for handles NOT in the abort set
but fail to finalize them. This caused subsequent Poll calls on those
handles to hang forever waiting for completions that had already been
consumed.

The fix adds an `is_being_aborted` flag to Posix_IOHandle that is set
when submitting the cancel request. When processing completions in
AbortIO, handles with this flag wait for req_count==2 (original + cancel),
while handles without the flag are finalized immediately at req_count==1.

Also refactored the completion finalization logic into a shared
FinalizeAsyncRead() helper function used by both Poll and AbortIO.

Reviewed By: mszeszko-meta, archang19

Differential Revision: D92230883

fbshipit-source-id: e6d11e009a4930e5608459771990f6cf7d46d827
2026-02-06 11:47:45 -08:00
mszeszko-meta 87d4b0d579 Merge pull request #14289 from facebook/cherry_pick_14280_to_10_11
Cherry picks into 10.11.fb
2026-02-02 10:47:06 -08:00
Adam Retter 09bc20b16e Fixes the Windows VS 2022 build (#14280)
Summary:
When building a Release on Windows RTTI is not available, so asserts that use dynamic_cast need to be disabled

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14280

Reviewed By: nmk70

Differential Revision: D91807791

Pulled By: mszeszko-meta

fbshipit-source-id: e29c19c757bcd076a1f09ed40b306bb50ba9e882
2026-01-30 16:36:19 -08:00
Peter Dillinger dd9a806edd Update HISTORY for 10.11.0 2026-01-26 12:42:18 -08:00
45 changed files with 933 additions and 100 deletions

No files matched your search

+4
View File
@@ -219,6 +219,10 @@ if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4996 /wd4100 /wd4324")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DNDEBUG")
message(STATUS "Setting /DNDEBUG as CMAKE_BUILD_TYPE is set to ${CMAKE_BUILD_TYPE}")
endif()
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing -Wno-invalid-offsetof")
+21
View File
@@ -1,6 +1,27 @@
# Rocksdb Change Log
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
## 10.11.2 (02/25/2026)
### Bug Fixes
* Fix longstanding failures that can arise from reading and/or compacting old DB dirs with range deletions (likely from version < 5.19.0) in many newer versions.
## 10.11.1 (02/06/2026)
### Bug Fixes
* Fixed a bug in PosixFS AbortIO that could cause it to ignore an IO completion and a subsequent Poll to hang
## 10.11.0 (01/23/2026)
### Public API Changes
* New SetOptions API that allows setting options for multiple CFs, avoiding the need to reserialize OPTIONS file for each CF
* Remove remaining pieces of Lua integration
### Behavior Changes
* The new default for `BlockBasedTableOptions::format_version` is 7, which has been supported since RocksDB 10.4.0 and is required in order to use CompressionManagers supporting custom compression types.
### Bug Fixes
* Fixed a small performance bug with `format_version=7` when decompressing formats other than Snappy and ZSTD.
* Fixed an infinite compaction loop bug with User-Defined Timestamps (UDT) where bottommost files were repeatedly marked for compaction even though their timestamp could not be collapsed.
* Bugfix for persisted UDT record sequence number zeroing logic.
## 10.10.0 (12/16/2025)
### Bug Fixes
* Fixed a bug in best-efforts recovery that causes use-after-free crashes when accessing SST files that were cached during the recovery.
+4
View File
@@ -34,6 +34,7 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
whitebox_crash_test_with_txn whitebox_crash_test_with_ts \
whitebox_crash_test_with_optimistic_txn \
whitebox_crash_test_with_tiered_storage \
crash_test_db_cleanup \
crash_test: $(DB_STRESS_CMD)
# Do not parallelize
@@ -161,6 +162,9 @@ whitebox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
$(CRASHTEST_PY) --optimistic_txn whitebox --random_kill_odd \
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
crash_test_db_cleanup: $(DB_STRESS_CMD)
$(DB_STRESS_CMD) --delete_dir_and_exit=$(TEST_TMPDIR)
# Old names DEPRECATED
crash_test_with_txn: crash_test_with_wc_txn
whitebox_crash_test_with_txn: whitebox_crash_test_with_wc_txn
+30 -1
View File
@@ -2438,6 +2438,32 @@ bool CompactionJob::UpdateInternalStatsFromInputFiles(
bool has_error = false;
const ReadOptions read_options(Env::IOActivity::kCompaction);
const auto& input_table_properties = compaction->GetInputTableProperties();
// Check all input files for old block-based SST format_version. Why? Old
// block-based SST files from roughly version 5.0 to 5.18 could produce
// inaccurate num_entries counts due to the evolution of its handling along
// with num_range_deletions. We have to disable some paranoid checks when
// compacting files from such an old release. However, we don't have great
// information to identify those files, so we heuristically over-approximate
// that set of files using
// (a) format_version < 5, which will be true for any files from RocksDB <
// 6.6.0 and should not be true for any recent production files
// (b) to avoid including non-block-based SST files (which still use older
// format_version markers, and do not support DeleteRange), we also require
// the presence of the user property "rocksdb.block.based.table.index.type",
// which was added in RocksDB 2.8 and is always present in block-based tables.
for (const auto& tp_pair : input_table_properties) {
if (tp_pair.second && tp_pair.second->format_version < 5) {
// Check for block-based table by looking for its index type property
const auto& user_props = tp_pair.second->user_collected_properties;
if (user_props.find(BlockBasedTablePropertyNames::kIndexType) !=
user_props.end()) {
job_stats_->has_accurate_num_input_records = false;
break;
}
}
}
for (int input_level = 0;
input_level < static_cast<int>(compaction->num_input_levels());
++input_level) {
@@ -2682,7 +2708,10 @@ Status CompactionJob::ReadTablePropertiesDirectly(
std::shared_ptr<const TableProperties>* tp) {
std::unique_ptr<FSRandomAccessFile> file;
std::string file_name = GetTableFileName(file_meta->fd.GetNumber());
Status s = ioptions.fs->NewRandomAccessFile(file_name, file_options_, &file,
FileOptions fopts = file_options_;
fopts.file_checksum = file_meta->file_checksum;
fopts.file_checksum_func_name = file_meta->file_checksum_func_name;
Status s = ioptions.fs->NewRandomAccessFile(file_name, fopts, &file,
nullptr /* dbg */);
if (!s.ok()) {
return s;
+4
View File
@@ -250,6 +250,10 @@ class CompactionJob {
// @param num_input_range_del if non-null, will be set to the number of range
// deletion entries in this compaction input.
//
// If any input file has potentially unreliable num_entries count (old SST
// files - details in implementation),
// job_stats_->has_accurate_num_input_records is set to false.
//
// Returns true iff internal_stats_.output_level_stats.num_input_records and
// num_input_range_del are calculated successfully.
//
+4 -4
View File
@@ -65,7 +65,7 @@ Status VerifySstFileChecksum(const Options& options,
}
Status VerifySstFileChecksumInternal(const Options& options,
const EnvOptions& env_options,
const FileOptions& file_options,
const ReadOptions& read_options,
const std::string& file_path,
const SequenceNumber& largest_seqno) {
@@ -74,8 +74,8 @@ Status VerifySstFileChecksumInternal(const Options& options,
InternalKeyComparator internal_comparator(options.comparator);
ImmutableOptions ioptions(options);
Status s = ioptions.fs->NewRandomAccessFile(
file_path, FileOptions(env_options), &file, nullptr);
Status s =
ioptions.fs->NewRandomAccessFile(file_path, file_options, &file, nullptr);
if (s.ok()) {
s = ioptions.fs->GetFileSize(file_path, IOOptions(), &file_size, nullptr);
} else {
@@ -94,7 +94,7 @@ Status VerifySstFileChecksumInternal(const Options& options,
const bool kImmortal = true;
auto reader_options = TableReaderOptions(
ioptions, options.prefix_extractor, options.compression_manager.get(),
env_options, internal_comparator, options.block_protection_bytes_per_key,
file_options, internal_comparator, options.block_protection_bytes_per_key,
false /* skip_filters */, !kImmortal, false /* force_direct_prefetch */,
-1 /* level */);
reader_options.largest_seqno = largest_seqno;
+2 -1
View File
@@ -5,10 +5,11 @@
#pragma once
#include "rocksdb/db.h"
#include "rocksdb/file_system.h"
namespace ROCKSDB_NAMESPACE {
Status VerifySstFileChecksumInternal(const Options& options,
const EnvOptions& env_options,
const FileOptions& file_options,
const ReadOptions& read_options,
const std::string& file_path,
const SequenceNumber& largest_seqno = 0);
+125
View File
@@ -242,6 +242,43 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
Status::Code::kNotSupported);
}
TEST_F(DBBasicTest, ReadOnlyDBFlushWAL) {
// Test that FlushWAL returns NotSupported on read-only DB, and that
// GetLiveFilesStorageInfo works correctly even with manual_wal_flush=true.
// This is a regression test for a bug where GetLiveFilesStorageInfo would
// crash on read-only DBs with manual_wal_flush=true because FlushWAL
// accessed logs_.back() on an empty deque.
auto options = CurrentOptions();
options.manual_wal_flush = true;
DestroyAndReopen(options);
ASSERT_OK(Put("foo", "v1"));
ASSERT_OK(Put("bar", "v2"));
ASSERT_OK(Flush());
ASSERT_OK(Put("baz", "v3")); // Unflushed data in WAL
Close();
// Reopen as read-only
ASSERT_OK(ReadOnlyReopen(options));
ASSERT_EQ("v1", Get("foo"));
ASSERT_EQ("v2", Get("bar"));
ASSERT_EQ("v3", Get("baz"));
// FlushWAL should return NotSupported (not crash)
ASSERT_EQ(db_->FlushWAL(/*sync=*/false).code(), Status::Code::kNotSupported);
ASSERT_EQ(db_->FlushWAL(/*sync=*/true).code(), Status::Code::kNotSupported);
// GetLiveFilesStorageInfo should succeed (previously crashed with
// manual_wal_flush=true because it called FlushWAL which accessed
// logs_.back() on empty deque)
LiveFilesStorageInfoOptions lfsi_opts;
lfsi_opts.wal_size_for_flush = 0;
std::vector<LiveFileStorageInfo> files;
ASSERT_OK(db_->GetLiveFilesStorageInfo(lfsi_opts, &files));
ASSERT_GT(files.size(), 0);
Close();
}
TEST_F(DBBasicTest, ReadOnlyDBWithWriteDBIdToManifestSet) {
auto options = CurrentOptions();
options.write_dbid_to_manifest = false;
@@ -5437,6 +5474,94 @@ INSTANTIATE_TEST_CASE_P(DBBasicTestDeadline, DBBasicTestDeadline,
::testing::Values(std::make_tuple(true, false),
std::make_tuple(false, true),
std::make_tuple(true, true)));
// FileSystemWrapper that captures FileOptions passed to NewRandomAccessFile
// for .sst files, so we can verify file_checksum fields are populated.
class ChecksumCapturingFS : public FileSystemWrapper {
public:
explicit ChecksumCapturingFS(const std::shared_ptr<FileSystem>& base)
: FileSystemWrapper(base) {}
static const char* kClassName() { return "ChecksumCapturingFS"; }
const char* Name() const override { return kClassName(); }
IOStatus NewRandomAccessFile(const std::string& fname,
const FileOptions& opts,
std::unique_ptr<FSRandomAccessFile>* result,
IODebugContext* dbg) override {
if (fname.find(".sst") != std::string::npos) {
std::lock_guard<std::mutex> lock(mu_);
captured_file_checksum_ = opts.file_checksum;
captured_file_checksum_func_name_ = opts.file_checksum_func_name;
capture_count_++;
}
return target()->NewRandomAccessFile(fname, opts, result, dbg);
}
std::string GetCapturedFileChecksum() {
std::lock_guard<std::mutex> lock(mu_);
return captured_file_checksum_;
}
std::string GetCapturedFileChecksumFuncName() {
std::lock_guard<std::mutex> lock(mu_);
return captured_file_checksum_func_name_;
}
int GetCaptureCount() {
std::lock_guard<std::mutex> lock(mu_);
return capture_count_;
}
void Reset() {
std::lock_guard<std::mutex> lock(mu_);
captured_file_checksum_.clear();
captured_file_checksum_func_name_.clear();
capture_count_ = 0;
}
private:
std::mutex mu_;
std::string captured_file_checksum_;
std::string captured_file_checksum_func_name_;
int capture_count_ = 0;
};
TEST_F(DBBasicTest, FileChecksumInFileOptions) {
// Verify that file_checksum and file_checksum_func_name from FileMetaData
// are propagated through FileOptions when opening SST files.
auto capturing_fs =
std::make_shared<ChecksumCapturingFS>(env_->GetFileSystem());
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, capturing_fs));
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.env = env.get();
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
DestroyAndReopen(options);
// Write data and flush to create an SST with a file checksum.
ASSERT_OK(Put("key1", "value1"));
ASSERT_OK(Flush());
// Reset captures, then reopen to trigger TableCache SST open.
capturing_fs->Reset();
Reopen(options);
// Read to trigger SST open through TableCache::GetTableReader.
ASSERT_EQ("value1", Get("key1"));
// Verify that checksum fields were populated.
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
ASSERT_FALSE(capturing_fs->GetCapturedFileChecksum().empty());
ASSERT_NE(capturing_fs->GetCapturedFileChecksumFuncName(),
capturing_fs->GetCapturedFileChecksum());
ASSERT_EQ(capturing_fs->GetCapturedFileChecksumFuncName(),
"FileChecksumCrc32c");
Close();
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
+8 -2
View File
@@ -6471,8 +6471,11 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
fmeta->file_checksum_func_name, fname,
read_options);
} else {
FileOptions fopts = file_options_;
fopts.file_checksum = fmeta->file_checksum;
fopts.file_checksum_func_name = fmeta->file_checksum_func_name;
s = ROCKSDB_NAMESPACE::VerifySstFileChecksumInternal(
opts, file_options_, read_options, fname, fd.largest_seqno);
opts, fopts, read_options, fname, fd.largest_seqno);
}
RecordTick(stats_, VERIFY_CHECKSUM_READ_BYTES,
IOSTATS(bytes_read) - prev_bytes_read);
@@ -6540,12 +6543,15 @@ Status DBImpl::VerifyFullFileChecksum(const std::string& file_checksum_expected,
}
std::string file_checksum;
std::string func_name;
FileOptions fopts;
fopts.file_checksum = file_checksum_expected;
fopts.file_checksum_func_name = func_name_expected;
s = ROCKSDB_NAMESPACE::GenerateOneFileChecksum(
fs_.get(), fname, immutable_db_options_.file_checksum_gen_factory.get(),
func_name_expected, &file_checksum, &func_name,
read_options.readahead_size, immutable_db_options_.allow_mmap_reads,
io_tracer_, immutable_db_options_.rate_limiter.get(), read_options,
immutable_db_options_.stats, immutable_db_options_.clock);
immutable_db_options_.stats, immutable_db_options_.clock, fopts);
if (s.ok()) {
assert(func_name_expected == func_name);
if (file_checksum != file_checksum_expected) {
+5
View File
@@ -121,6 +121,11 @@ class DBImplReadOnly : public DBImpl {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DBImpl::FlushWAL;
Status FlushWAL(const FlushWALOptions& /*options*/) override {
return Status::NotSupported("Not supported operation in read only mode.");
}
using DB::IngestExternalFile;
Status IngestExternalFile(
ColumnFamilyHandle* /*column_family*/,
+16 -2
View File
@@ -300,6 +300,18 @@ Status ExternalSstFileIngestionJob::Prepare(
// ingestion.
// TODO: plumb Env::IOActivity
ReadOptions ro;
// Pass user-provided checksums through FileOptions when available.
// The caller may not have provided checksums at all (empty vectors),
// so we guard with a bounds check.
FileOptions fopts;
if (i < files_checksums.size()) {
fopts.file_checksum = files_checksums[i];
}
if (i < files_checksum_func_names.size()) {
fopts.file_checksum_func_name = files_checksum_func_names[i];
} else {
fopts.file_checksum_func_name = kNoFileChecksumFuncName;
}
IOStatus io_s = GenerateOneFileChecksum(
fs_.get(), files_to_ingest_[i].internal_file_path,
db_options_.file_checksum_gen_factory.get(),
@@ -308,7 +320,7 @@ Status ExternalSstFileIngestionJob::Prepare(
ingestion_options_.verify_checksums_readahead_size,
db_options_.allow_mmap_reads, io_tracer_,
db_options_.rate_limiter.get(), ro, db_options_.stats,
db_options_.clock);
db_options_.clock, fopts);
if (!io_s.ok()) {
status = io_s;
ROCKS_LOG_WARN(db_options_.info_log,
@@ -1482,13 +1494,15 @@ IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
// TODO: rate limit file reads for checksum calculation during file ingestion.
// TODO: plumb Env::IOActivity
ReadOptions ro;
FileOptions gen_fopts;
gen_fopts.file_checksum_func_name = kNoFileChecksumFuncName;
IOStatus io_s = GenerateOneFileChecksum(
fs_.get(), file_to_ingest->internal_file_path,
db_options_.file_checksum_gen_factory.get(), requested_checksum_func_name,
&file_checksum, &file_checksum_func_name,
ingestion_options_.verify_checksums_readahead_size,
db_options_.allow_mmap_reads, io_tracer_, db_options_.rate_limiter.get(),
ro, db_options_.stats, db_options_.clock);
ro, db_options_.stats, db_options_.clock, gen_fopts);
if (!io_s.ok()) {
ROCKS_LOG_WARN(
db_options_.info_log, "Failed to generate checksum for %s: %s",
+4 -2
View File
@@ -310,8 +310,10 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
std::unique_ptr<FSRandomAccessFile> sst_file;
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
status =
fs_->NewRandomAccessFile(external_file, env_options_, &sst_file, nullptr);
FileOptions fo{env_options_};
fo.file_checksum = file_meta.file_checksum;
fo.file_checksum_func_name = file_meta.file_checksum_func_name;
status = fs_->NewRandomAccessFile(external_file, fo, &sst_file, nullptr);
if (!status.ok()) {
return status;
}
+3 -2
View File
@@ -98,6 +98,8 @@ Status TableCache::GetTableReader(
std::unique_ptr<FSRandomAccessFile> file;
FileOptions fopts = file_options;
fopts.temperature = file_temperature;
fopts.file_checksum = file_meta.file_checksum;
fopts.file_checksum_func_name = file_meta.file_checksum_func_name;
Status s = PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
TEST_SYNC_POINT_CALLBACK("TableCache::GetTableReader:BeforeOpenFile",
const_cast<Status*>(&s));
@@ -113,8 +115,7 @@ Status TableCache::GetTableReader(
Status temp_s =
PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
if (temp_s.ok()) {
temp_s = ioptions_.fs->NewRandomAccessFile(fname, file_options, &file,
nullptr);
temp_s = ioptions_.fs->NewRandomAccessFile(fname, fopts, &file, nullptr);
}
if (temp_s.ok()) {
RecordTick(ioptions_.stats, NO_FILE_OPENS);
+5 -3
View File
@@ -1820,8 +1820,10 @@ Status Version::GetTableProperties(const ReadOptions& read_options,
file_name = TableFileName(ioptions.cf_paths, file_meta->fd.GetNumber(),
file_meta->fd.GetPathId());
}
s = ioptions.fs->NewRandomAccessFile(file_name, file_options_, &file,
nullptr);
FileOptions fopts = file_options_;
fopts.file_checksum = file_meta->file_checksum;
fopts.file_checksum_func_name = file_meta->file_checksum_func_name;
s = ioptions.fs->NewRandomAccessFile(file_name, fopts, &file, nullptr);
if (!s.ok()) {
return s;
}
@@ -3444,7 +3446,7 @@ bool Version::MaybeInitializeFileMetaData(const ReadOptions& read_options,
// Ensure new invariants on old files
file_meta->num_deletions =
std::max(tp->num_deletions, tp->num_range_deletions);
file_meta->num_entries = std::max(tp->num_entries, tp->num_deletions);
file_meta->num_entries = std::max(tp->num_entries, file_meta->num_deletions);
return true;
}
+1
View File
@@ -101,6 +101,7 @@ DECLARE_bool(verify_before_write);
DECLARE_bool(histogram);
DECLARE_bool(destroy_db_initially);
DECLARE_bool(destroy_db_and_exit);
DECLARE_string(delete_dir_and_exit);
DECLARE_bool(verbose);
DECLARE_bool(progress_reports);
DECLARE_uint64(db_write_buffer_size);
+32
View File
@@ -9,8 +9,11 @@
#ifdef GFLAGS
#pragma once
#include "db_stress_tool/db_stress_common.h"
#include "file/filename.h"
#include "monitoring/thread_status_util.h"
#include "rocksdb/file_checksum.h"
namespace ROCKSDB_NAMESPACE {
namespace {
@@ -173,6 +176,35 @@ class DbStressFSWrapper : public FileSystemWrapper {
const FileOptions& file_opts,
std::unique_ptr<FSRandomAccessFile>* r,
IODebugContext* dbg) override {
// verify that file checksums are propagated through FileOptions
// for SST file opens.
std::string basename = f.substr(f.rfind('/') + 1);
uint64_t file_number;
FileType file_type;
if (ParseFileName(basename, &file_number, &file_type) &&
file_type == kTableFile) {
// file_checksum_func_name must always be populated to be sure each call
// site within RocksDB is intentional about populating the fields with the
// best available information:
// - kNoFileChecksumFuncName: no checksum context available
// (e.g., SstFileDumper, SstFileReader, checksum generation),
// always paired with empty checksum
// - kUnknownFileChecksumFuncName: file created without a
// checksum factory (from MANIFEST), always paired with
// empty checksum
// - a real name (e.g., "FileChecksumCrc32c"): checksum exists
assert(!file_opts.file_checksum_func_name.empty());
if (file_opts.file_checksum_func_name == kUnknownFileChecksumFuncName ||
file_opts.file_checksum_func_name == kNoFileChecksumFuncName) {
// No checksum available — checksum value must be empty
assert(file_opts.file_checksum.empty());
} else {
// A real checksum function — checksum value must be present
assert(!file_opts.file_checksum.empty());
}
}
std::unique_ptr<FSRandomAccessFile> file;
IOStatus s = target()->NewRandomAccessFile(f, file_opts, &file, dbg);
if (s.ok()) {
+4
View File
@@ -139,6 +139,10 @@ DEFINE_bool(destroy_db_and_exit, false,
"Destroys the database dir and exits. Useful for cleanup without "
"running stress test. Other options are mostly ignored.");
DEFINE_string(delete_dir_and_exit, "",
"Recursively deletes the specified directory and exits. "
"Useful for cleaning up TEST_TMPDIR after crash tests.");
DEFINE_bool(verbose, false, "Verbose");
DEFINE_bool(progress_reports, true,
+18 -2
View File
@@ -94,8 +94,10 @@ int db_stress_tool(int argc, char** argv) {
raw_env = fault_env_guard.get();
}
env_wrapper_guard = std::make_shared<CompositeEnvWrapper>(
raw_env, std::make_shared<DbStressFSWrapper>(raw_env->GetFileSystem()));
auto db_stress_fs =
std::make_shared<DbStressFSWrapper>(raw_env->GetFileSystem());
env_wrapper_guard =
std::make_shared<CompositeEnvWrapper>(raw_env, db_stress_fs);
db_stress_env = env_wrapper_guard.get();
// Handle --destroy_db_and_exit early, before other option validation
@@ -111,6 +113,20 @@ int db_stress_tool(int argc, char** argv) {
}
}
// Handle --delete_dir_and_exit early, before other option validation
if (!FLAGS_delete_dir_and_exit.empty()) {
s = DestroyDir(raw_env, FLAGS_delete_dir_and_exit);
if (s.ok()) {
fprintf(stdout, "Successfully deleted directory %s\n",
FLAGS_delete_dir_and_exit.c_str());
return 0;
} else {
fprintf(stderr, "Failed to delete directory %s: %s\n",
FLAGS_delete_dir_and_exit.c_str(), s.ToString().c_str());
return 1;
}
}
FLAGS_rep_factory = StringToRepFactory(FLAGS_memtablerep.c_str());
// The number of background threads should be at least as much the
+193 -24
View File
@@ -41,6 +41,9 @@
#include "env/env_chroot.h"
#include "env/env_encryption_ctr.h"
#include "env/fs_readonly.h"
#if defined(ROCKSDB_IOURING_PRESENT)
#include "env/io_posix.h"
#endif
#include "env/mock_env.h"
#include "env/unique_id_gen.h"
#include "logging/log_buffer.h"
@@ -3741,32 +3744,35 @@ void TestAbortIOWithRequests(
std::shared_ptr<FileSystem> fs = env->GetFileSystem();
std::string fname = test::PerThreadDBPath(env, "testfile_abortio");
constexpr size_t kSectorSize = 4096;
// 1. Create test file once (content doesn't change between iterations)
{
std::unique_ptr<FSWritableFile> wfile;
FileOptions file_opts;
file_opts.use_direct_writes = true;
ASSERT_OK(fs->NewWritableFile(fname, file_opts, &wfile, nullptr));
for (int iter = 0; iter < iterations; iter++) {
// 1. Create test file of specified size using direct IO
{
std::unique_ptr<FSWritableFile> wfile;
FileOptions file_opts;
file_opts.use_direct_writes = true;
ASSERT_OK(fs->NewWritableFile(fname, file_opts, &wfile, nullptr));
// Query the file's required buffer alignment (logical block size)
// instead of hardcoding 4096, to support devices with different
// sector sizes.
size_t sector_size = wfile->GetRequiredBufferAlignment();
// Round up to full sectors for direct IO writes
size_t num_sectors = (file_size + kSectorSize - 1) / kSectorSize;
for (size_t i = 0; i < num_sectors; ++i) {
auto data = NewAligned(kSectorSize, static_cast<char>(i + 1));
Slice slice(data.get(), kSectorSize);
ASSERT_OK(wfile->Append(slice, IOOptions(), nullptr));
}
// Truncate to exact file size if not aligned to sector boundary
if (file_size % kSectorSize != 0) {
ASSERT_OK(wfile->Truncate(file_size, IOOptions(), nullptr));
}
ASSERT_OK(wfile->Close(IOOptions(), nullptr));
// Round up to full sectors for direct IO writes
size_t num_sectors = (file_size + sector_size - 1) / sector_size;
for (size_t i = 0; i < num_sectors; ++i) {
auto data = NewAligned(sector_size, static_cast<char>(i + 1));
Slice slice(data.get(), sector_size);
ASSERT_OK(wfile->Append(slice, IOOptions(), nullptr));
}
// Truncate to exact file size if not aligned to sector boundary
if (file_size % sector_size != 0) {
ASSERT_OK(wfile->Truncate(file_size, IOOptions(), nullptr));
}
ASSERT_OK(wfile->Close(IOOptions(), nullptr));
}
for (int iter = 0; iter < iterations; iter++) {
// 2. Submit ReadAsync requests and immediately abort
{
FileOptions file_opts;
@@ -3781,6 +3787,7 @@ void TestAbortIOWithRequests(
std::vector<std::unique_ptr<char, Deleter>> data;
std::vector<size_t> vals;
IOHandleDeleter del_fn;
std::atomic<int> callbacks_invoked{0};
// Initialize read requests from specs
for (size_t i = 0; i < num_reads; i++) {
@@ -3796,6 +3803,7 @@ void TestAbortIOWithRequests(
[&](FSReadRequest& req, void* cb_arg) {
size_t i = *(reinterpret_cast<size_t*>(cb_arg));
reqs[i].status = req.status;
callbacks_invoked++;
};
// Submit all ReadAsync requests
@@ -3822,6 +3830,15 @@ void TestAbortIOWithRequests(
// Immediately call AbortIO - this should NOT hang
ASSERT_OK(fs->AbortIO(io_handles));
// Verify all handles are finished and all callbacks were invoked.
// Since all handles are passed to AbortIO, every handle is guaranteed
// to be finalized (either completed or cancelled).
for (size_t i = 0; i < num_reads; i++) {
Posix_IOHandle* h = static_cast<Posix_IOHandle*>(io_handles[i]);
ASSERT_TRUE(h->is_finished);
}
ASSERT_EQ(callbacks_invoked.load(), static_cast<int>(num_reads));
// Clean up handles
for (size_t i = 0; i < num_reads; i++) {
if (io_handles[i]) {
@@ -3829,10 +3846,10 @@ void TestAbortIOWithRequests(
}
}
}
ASSERT_OK(fs->DeleteFile(fname, IOOptions(), nullptr));
}
ASSERT_OK(fs->DeleteFile(fname, IOOptions(), nullptr));
fprintf(stderr, "TestAbortIOWithRequests: completed %d iterations\n",
iterations);
#else
@@ -3919,6 +3936,158 @@ TEST_F(TestAsyncRead, AbortIOReversedHandles) {
/*use_direct_io=*/true, /*iterations=*/100);
}
// Test for bug fix: AbortIO with partial handles should correctly handle
// completions for non-aborted handles.
//
// Previously, AbortIO would consume completions for non-aborted handles but
// not set is_finished (since it expected req_count==2 for all handles).
// This caused subsequent Poll calls to hang forever.
//
// The fix correctly detects handles not in the abort set and finalizes them
// immediately when their completion arrives (at req_count==1).
TEST_F(TestAsyncRead, AbortIOPartialHandlesBug) {
#if defined(ROCKSDB_IOURING_PRESENT)
std::shared_ptr<FileSystem> fs = env_->GetFileSystem();
std::string fname = test::PerThreadDBPath(env_, "testfile_abortio_partial");
constexpr size_t kSectorSize = 4096;
constexpr size_t kFileSize = 2 * 1024 * 1024; // 2MB
// 1. Create test file with direct I/O
{
std::unique_ptr<FSWritableFile> wfile;
FileOptions file_opts;
file_opts.use_direct_writes = true;
ASSERT_OK(fs->NewWritableFile(fname, file_opts, &wfile, nullptr));
size_t num_sectors = kFileSize / kSectorSize;
for (size_t i = 0; i < num_sectors; ++i) {
auto data = NewAligned(kSectorSize, static_cast<char>(i + 1));
Slice slice(data.get(), kSectorSize);
ASSERT_OK(wfile->Append(slice, IOOptions(), nullptr));
}
ASSERT_OK(wfile->Close(IOOptions(), nullptr));
}
// 2. Submit 3 ReadAsync requests, abort only the first one, then Poll the
// rest
{
FileOptions file_opts;
file_opts.use_direct_reads = true;
std::unique_ptr<FSRandomAccessFile> file;
ASSERT_OK(fs->NewRandomAccessFile(fname, file_opts, &file, nullptr));
IOOptions opts;
constexpr size_t kNumReads = 3;
std::vector<void*> io_handles(kNumReads);
std::vector<FSReadRequest> reqs(kNumReads);
std::vector<std::unique_ptr<char, Deleter>> data;
std::vector<size_t> vals;
IOHandleDeleter del_fn;
std::atomic<int> callbacks_invoked{0};
// H0: 1MB read, H1: 4KB read, H2: 4KB read
std::vector<std::pair<uint64_t, size_t>> read_specs = {
{0, 1024 * 1024}, // H0: 1MB at offset 0
{1024 * 1024, 4096}, // H1: 4KB at offset 1MB
{1024 * 1024 + 4096, 4096}, // H2: 4KB at offset 1MB+4KB
};
for (size_t i = 0; i < kNumReads; i++) {
reqs[i].offset = read_specs[i].first;
reqs[i].len = read_specs[i].second;
data.emplace_back(NewAligned(reqs[i].len, 0));
reqs[i].scratch = data.back().get();
vals.push_back(i);
}
std::function<void(FSReadRequest&, void*)> callback =
[&](FSReadRequest& req, void* cb_arg) {
size_t i = *(reinterpret_cast<size_t*>(cb_arg));
reqs[i].status = req.status;
callbacks_invoked++;
};
// Submit all ReadAsync requests
for (size_t i = 0; i < kNumReads; i++) {
void* cb_arg = static_cast<void*>(&(vals[i]));
IOStatus s = file->ReadAsync(reqs[i], opts, callback, cb_arg,
&(io_handles[i]), &del_fn, nullptr);
if (s.IsNotSupported()) {
// io_uring not supported, clean up and skip
for (size_t j = 0; j < i; j++) {
if (io_handles[j]) {
del_fn(io_handles[j]);
}
}
ASSERT_OK(fs->DeleteFile(fname, IOOptions(), nullptr));
return;
}
ASSERT_OK(s);
}
// Wait for reads to complete in io_uring (completions in queue but not
// consumed). 5 seconds should be plenty for direct I/O reads to complete.
std::this_thread::sleep_for(std::chrono::seconds(5));
// Abort ONLY H0 - this will consume all completions but should correctly
// finalize H1 and H2 (since they're not in the abort set).
std::vector<void*> abort_handles = {io_handles[0]};
ASSERT_OK(fs->AbortIO(abort_handles));
// Verify H0 is finished (aborted)
Posix_IOHandle* h0 = static_cast<Posix_IOHandle*>(io_handles[0]);
ASSERT_TRUE(h0->is_finished);
ASSERT_EQ(h0->req_count, 2u); // original + cancel
// Note: H1 and H2 may or may not be finished at this point. AbortIO
// finalizes non-aborted handles whose CQEs arrive while waiting for
// aborted handles, but CQE ordering is non-deterministic. If H0's
// completions arrived first, H1/H2's CQEs are still in the queue.
// Poll handles either case correctly.
// Poll on H1, H2 - completes them if not already finalized by AbortIO
std::vector<void*> poll_handles = {io_handles[1], io_handles[2]};
// Use a watchdog to detect hang (regression test for the original bug
// where AbortIO consumed non-aborted CQEs without finalizing them)
std::atomic<bool> poll_completed{false};
std::thread watchdog([&]() {
for (int i = 0; i < 500; i++) { // 5 seconds timeout
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (poll_completed) return;
}
// Bug regression: Poll hung
_exit(1);
});
fs->Poll(poll_handles, poll_handles.size());
poll_completed = true;
watchdog.join();
// After Poll, H1 and H2 must be finished
Posix_IOHandle* h1 = static_cast<Posix_IOHandle*>(io_handles[1]);
Posix_IOHandle* h2 = static_cast<Posix_IOHandle*>(io_handles[2]);
ASSERT_TRUE(h1->is_finished);
ASSERT_TRUE(h2->is_finished);
// Verify all callbacks were invoked
ASSERT_EQ(callbacks_invoked.load(), 3);
// Clean up handles
for (size_t i = 0; i < kNumReads; i++) {
if (io_handles[i]) {
del_fn(io_handles[i]);
}
}
}
ASSERT_OK(fs->DeleteFile(fname, IOOptions(), nullptr));
#else
(void)env_; // Suppress unused variable warning
#endif
}
struct StaticDestructionTester {
bool activated = false;
~StaticDestructionTester() {
+20 -25
View File
@@ -1129,25 +1129,7 @@ class PosixFileSystem : public FileSystem {
// Reset cqe data to catch any stray reuse of it
static_cast<struct io_uring_cqe*>(cqe)->user_data = 0xd5d5d5d5d5d5d5d5;
FSReadRequest req;
req.scratch = posix_handle->scratch;
req.offset = posix_handle->offset;
req.len = posix_handle->len;
size_t finished_len = 0;
size_t bytes_read = 0;
bool read_again = false;
UpdateResult(cqe, "", req.len, posix_handle->iov.iov_len,
true /*async_read*/, posix_handle->use_direct_io,
posix_handle->alignment, finished_len, &req, bytes_read,
read_again);
posix_handle->is_finished = true;
io_uring_cqe_seen(iu, cqe);
posix_handle->cb(req, posix_handle->cb_arg);
(void)finished_len;
(void)bytes_read;
(void)read_again;
FinalizeAsyncRead(iu, cqe, posix_handle);
if (static_cast<Posix_IOHandle*>(io_handles[i]) == posix_handle) {
break;
@@ -1188,6 +1170,11 @@ class PosixFileSystem : public FileSystem {
return IOStatus::IOError("");
}
// Mark this handle as being aborted. This is used when processing
// completions to distinguish between aborted handles (expect 2
// completions: original + cancel) and non-aborted handles (expect 1).
posix_handle->is_being_aborted = true;
// Prepare the cancel request.
struct io_uring_sqe* sqe;
sqe = io_uring_get_sqe(iu);
@@ -1234,6 +1221,14 @@ class PosixFileSystem : public FileSystem {
}
posix_handle->req_count++;
if (!posix_handle->is_being_aborted) {
// This is a completion for a handle NOT being aborted.
// It only has 1 outstanding request (the original read), so we
// should finalize it now.
FinalizeAsyncRead(iu, cqe, posix_handle);
continue;
}
// Reset cqe data to catch any stray reuse of it
static_cast<struct io_uring_cqe*>(cqe)->user_data = 0xd5d5d5d5d5d5d5d5;
io_uring_cqe_seen(iu, cqe);
@@ -1247,8 +1242,9 @@ class PosixFileSystem : public FileSystem {
// - And finally, if the request to cancel wasn't
// found, the cancel request is completed with -ENOENT.
//
// Every handle has to wait for 2 requests completion: original one and
// the cancel request which is tracked by PosixHandle::req_count.
// Every handle being aborted has to wait for 2 requests completion:
// original one and the cancel request which is tracked by
// PosixHandle::req_count.
// Note: We must mark is_finished and invoke the callback for ANY handle
// that reaches req_count == 2, not just the one we're currently waiting
// for (io_handles[i]). Otherwise, if completions arrive out of order,
@@ -1278,7 +1274,7 @@ class PosixFileSystem : public FileSystem {
void SupportedOps(int64_t& supported_ops) override {
supported_ops = 0;
#if defined(ROCKSDB_IOURING_PRESENT)
if (IsIOUringEnabled()) {
if (IsIOUringEnabled() && thread_local_async_read_io_urings_) {
// Underlying FS supports async_io
supported_ops |= (1 << FSSupportedOps::kAsyncIO);
}
@@ -1344,9 +1340,8 @@ PosixFileSystem::PosixFileSystem()
page_size_(getpagesize()),
allow_non_owner_access_(true) {
#if defined(ROCKSDB_IOURING_PRESENT)
// Test whether IOUring is supported, and if it does, create a managing
// object for thread local point so that in the future thread-local
// io_uring can be created.
// Test whether IOUring is supported with the same flags that ReadAsync and
// MultiRead will use at runtime.
struct io_uring* new_io_uring = CreateIOUring();
if (new_io_uring != nullptr) {
thread_local_async_read_io_urings_.reset(new ThreadLocalPtr(DeleteIOUring));
+2 -8
View File
@@ -755,10 +755,7 @@ IOStatus PosixRandomAccessFile::MultiRead(FSReadRequest* reqs, size_t num_reqs,
iu = static_cast<struct io_uring*>(
thread_local_multi_read_io_urings_->Get());
if (iu == nullptr) {
unsigned int flags = 0;
flags |= IORING_SETUP_SINGLE_ISSUER;
flags |= IORING_SETUP_DEFER_TASKRUN;
iu = CreateIOUring(flags);
iu = CreateIOUring();
if (iu != nullptr) {
thread_local_multi_read_io_urings_->Reset(iu);
}
@@ -1090,10 +1087,7 @@ IOStatus PosixRandomAccessFile::ReadAsync(
iu = static_cast<struct io_uring*>(
thread_local_async_read_io_urings_->Get());
if (iu == nullptr) {
unsigned int flags = 0;
flags |= IORING_SETUP_SINGLE_ISSUER;
flags |= IORING_SETUP_DEFER_TASKRUN;
iu = CreateIOUring(flags);
iu = CreateIOUring();
if (iu != nullptr) {
thread_local_async_read_io_urings_->Reset(iu);
}
+30 -1
View File
@@ -127,6 +127,7 @@ struct Posix_IOHandle {
use_direct_io(_use_direct_io),
alignment(_alignment),
is_finished(false),
is_being_aborted(false),
req_count(0) {}
struct iovec iov;
@@ -139,6 +140,10 @@ struct Posix_IOHandle {
bool use_direct_io;
size_t alignment;
bool is_finished;
// is_being_aborted is set by AbortIO when a cancel request is submitted.
// Used to distinguish between aborted handles (expect 2 completions) and
// non-aborted handles (expect 1 completion) when processing completions.
bool is_being_aborted;
// req_count is used by AbortIO API to keep track of number of requests.
uint32_t req_count;
};
@@ -197,6 +202,27 @@ inline void UpdateResult(struct io_uring_cqe* cqe, const std::string& file_name,
(void)len;
#endif
}
// Finalize a completed async read request.
// Processes the CQE result, marks the handle as finished, and invokes the
// callback. This is shared between Poll and AbortIO (for non-aborted handles).
inline void FinalizeAsyncRead(struct io_uring* iu, struct io_uring_cqe* cqe,
Posix_IOHandle* posix_handle) {
FSReadRequest req;
req.scratch = posix_handle->scratch;
req.offset = posix_handle->offset;
req.len = posix_handle->len;
size_t finished_len = 0;
size_t bytes_read = 0;
bool read_again = false;
UpdateResult(cqe, "", req.len, posix_handle->iov.iov_len, true /*async_read*/,
posix_handle->use_direct_io, posix_handle->alignment,
finished_len, &req, bytes_read, read_again);
posix_handle->is_finished = true;
io_uring_cqe_seen(iu, cqe);
posix_handle->cb(req, posix_handle->cb_arg);
}
#endif
#ifdef OS_LINUX
@@ -307,8 +333,11 @@ inline void DeleteIOUring(void* p) {
delete iu;
}
inline struct io_uring* CreateIOUring(unsigned int flags = 0) {
inline struct io_uring* CreateIOUring() {
struct io_uring* new_io_uring = new struct io_uring;
unsigned int flags = 0;
flags |= IORING_SETUP_SINGLE_ISSUER;
flags |= IORING_SETUP_DEFER_TASKRUN;
int ret = io_uring_queue_init(kIoUringDepth, new_io_uring, flags);
if (ret) {
delete new_io_uring;
+20 -4
View File
@@ -351,7 +351,7 @@ void FilePrefetchBuffer::ClearOutdatedData(uint64_t offset, size_t length) {
assert(IsBufferQueueEmpty() || buf->IsOffsetInBuffer(offset));
}
void FilePrefetchBuffer::PollIfNeeded(uint64_t offset, size_t length) {
Status FilePrefetchBuffer::PollIfNeeded(uint64_t offset, size_t length) {
BufferInfo* buf = GetFirstBuffer();
if (buf->async_read_in_progress_ && fs_ != nullptr) {
@@ -362,7 +362,16 @@ void FilePrefetchBuffer::PollIfNeeded(uint64_t offset, size_t length) {
std::vector<void*> handles;
handles.emplace_back(buf->io_handle_);
StopWatch sw(clock_, stats_, POLL_WAIT_MICROS);
fs_->Poll(handles, 1).PermitUncheckedError();
IOStatus io_s = fs_->Poll(handles, 1);
// Allow tests to inject Poll errors
TEST_SYNC_POINT_CALLBACK("FilePrefetchBuffer::PollIfNeeded:IOStatus",
&io_s);
if (!io_s.ok()) {
// On Poll failure, clean up the handle and abort.
// DestroyAndClearIOHandle also sets async_read_in_progress_ to false.
DestroyAndClearIOHandle(buf);
return io_s;
}
}
// Reset and Release io_handle after the Poll API as request has been
@@ -373,6 +382,7 @@ void FilePrefetchBuffer::PollIfNeeded(uint64_t offset, size_t length) {
// Always call outdated data after Poll as Buffers might be out of sync w.r.t
// offset and length.
ClearOutdatedData(offset, length);
return Status::OK();
}
// ReadAheadSizeTuning API calls readaheadsize_cb_
@@ -511,7 +521,10 @@ Status FilePrefetchBuffer::HandleOverlappingAsyncData(
// by Seek, but the next access is at another offset.
if (buf->async_read_in_progress_ &&
buf->IsOffsetInBufferWithAsyncProgress(offset)) {
PollIfNeeded(offset, length);
Status poll_status = PollIfNeeded(offset, length);
if (!poll_status.ok()) {
return poll_status;
}
}
if (IsBufferQueueEmpty() || NumBuffersAllocated() == 1) {
@@ -646,7 +659,10 @@ Status FilePrefetchBuffer::PrefetchInternal(const IOOptions& opts,
return s;
}
} else {
PollIfNeeded(tmp_offset, tmp_length);
Status poll_status = PollIfNeeded(tmp_offset, tmp_length);
if (!poll_status.ok()) {
return poll_status;
}
}
AllocateBufferIfEmpty();
+1 -1
View File
@@ -431,7 +431,7 @@ class FilePrefetchBuffer {
void ClearOutdatedData(uint64_t offset, size_t len);
// It calls Poll API to check for any pending asynchronous request.
void PollIfNeeded(uint64_t offset, size_t len);
Status PollIfNeeded(uint64_t offset, size_t len);
Status PrefetchInternal(const IOOptions& opts, RandomAccessFileReader* reader,
uint64_t offset, size_t length, size_t readahead_size,
+8 -2
View File
@@ -178,7 +178,8 @@ IOStatus GenerateOneFileChecksum(
std::string* file_checksum_func_name,
size_t verify_checksums_readahead_size, bool /*allow_mmap_reads*/,
std::shared_ptr<IOTracer>& io_tracer, RateLimiter* rate_limiter,
const ReadOptions& read_options, Statistics* stats, SystemClock* clock) {
const ReadOptions& read_options, Statistics* stats, SystemClock* clock,
const FileOptions& file_options) {
if (checksum_factory == nullptr) {
return IOStatus::InvalidArgument("Checksum factory is invalid");
}
@@ -218,7 +219,12 @@ IOStatus GenerateOneFileChecksum(
std::unique_ptr<RandomAccessFileReader> reader;
{
std::unique_ptr<FSRandomAccessFile> r_file;
io_s = fs->NewRandomAccessFile(file_path, FileOptions(), &r_file, nullptr);
FileOptions fopts = file_options;
if (fopts.file_checksum.empty()) {
// No expected checksum is known — this is a from-scratch computation.
fopts.file_checksum_func_name = kNoFileChecksumFuncName;
}
io_s = fs->NewRandomAccessFile(file_path, fopts, &r_file, nullptr);
if (!io_s.ok()) {
return io_s;
}
+2 -1
View File
@@ -83,7 +83,8 @@ IOStatus GenerateOneFileChecksum(
std::string* file_checksum_func_name,
size_t verify_checksums_readahead_size, bool allow_mmap_reads,
std::shared_ptr<IOTracer>& io_tracer, RateLimiter* rate_limiter,
const ReadOptions& read_options, Statistics* stats, SystemClock* clock);
const ReadOptions& read_options, Statistics* stats, SystemClock* clock,
const FileOptions& file_options);
inline IOStatus PrepareIOFromReadOptions(const ReadOptions& ro,
SystemClock* clock, IOOptions& opts,
+251
View File
@@ -2517,6 +2517,187 @@ TEST_P(PrefetchTest1, SeekParallelizationTest) {
Close();
}
TEST_P(PrefetchTest1, PollErrorRecoveryDuringIteration) {
// This end-to-end test verifies that Poll() errors during async prefetching
// are properly propagated to the iterator. When Poll() fails, the iterator
// should stop and return an IOError status.
//
// With error injection on the 3rd Poll call, the iterator reads ~231 keys
// (out of 500) before encountering the error.
if (mem_env_ || encrypted_env_) {
ROCKSDB_GTEST_SKIP("Test requires non-mem or non-encrypted environment");
return;
}
const int kNumKeys = 500;
std::shared_ptr<MockFS> fs = std::make_shared<MockFS>(
FileSystem::Default(), /*support_prefetch=*/false);
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, fs));
bool use_direct_io = GetParam();
Options options;
SetGenericOptions(env.get(), use_direct_io, options);
options.statistics = CreateDBStatistics();
BlockBasedTableOptions table_options;
SetBlockBasedTableOptions(table_options);
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
Status s = TryReopen(options);
if (use_direct_io && (s.IsNotSupported() || s.IsInvalidArgument())) {
ROCKSDB_GTEST_SKIP("Direct IO not supported");
return;
}
ASSERT_OK(s);
// Write keys with known values so we can verify correctness
std::map<std::string, std::string> expected_data;
{
WriteBatch batch;
for (int i = 0; i < kNumKeys; i++) {
std::string key = BuildKey(i);
std::string value = "value_" + std::to_string(i) + "_" +
std::string(100, 'x'); // Make values ~110 bytes
ASSERT_OK(batch.Put(key, value));
expected_data[key] = value;
}
ASSERT_OK(db_->Write(WriteOptions(), &batch));
ASSERT_OK(Flush());
}
std::string start_key = BuildKey(0);
std::string end_key = BuildKey(kNumKeys - 1);
Slice least(start_key.data(), start_key.size());
Slice greatest(end_key.data(), end_key.size());
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &least, &greatest));
// Set up callbacks to track async IO and inject Poll errors
std::atomic<int> poll_call_count{0};
std::atomic<int> poll_error_injected_count{0};
bool read_async_called = false;
SyncPoint::GetInstance()->SetCallBack(
"FilePrefetchBuffer::PollIfNeeded:IOStatus", [&](void* arg) {
poll_call_count++;
int current_count = poll_call_count.load();
// Inject error on the third Poll call to allow some keys to be read
// first
if (current_count == 3) {
IOStatus* io_s = static_cast<IOStatus*>(arg);
*io_s = IOStatus::IOError("Injected Poll error for e2e testing");
poll_error_injected_count++;
std::cout << "PollErrorRecoveryDuringIteration: Injected error on "
"Poll call #"
<< current_count << std::endl;
}
});
SyncPoint::GetInstance()->SetCallBack(
"UpdateResults::io_uring_result",
[&](void* /*arg*/) { read_async_called = true; });
SyncPoint::GetInstance()->EnableProcessing();
// Iterate through all keys with async IO enabled
ReadOptions ro;
ro.async_io = true;
ro.adaptive_readahead = true;
int keys_read = 0;
int data_mismatches = 0;
Status iter_status;
{
auto iter = std::unique_ptr<Iterator>(db_->NewIterator(ro));
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
std::string key = iter->key().ToString();
std::string value = iter->value().ToString();
auto it = expected_data.find(key);
if (it == expected_data.end()) {
std::cout << "PollErrorRecoveryDuringIteration: Unexpected key: " << key
<< std::endl;
data_mismatches++;
} else if (it->second != value) {
std::cout << "PollErrorRecoveryDuringIteration: Value mismatch for key "
<< key << std::endl;
data_mismatches++;
}
keys_read++;
}
iter_status = iter->status();
}
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
// Log results
std::cout << "PollErrorRecoveryDuringIteration: " << "read_async_called="
<< read_async_called << ", poll_calls=" << poll_call_count.load()
<< ", poll_errors_injected=" << poll_error_injected_count.load()
<< ", keys_read=" << keys_read << ", expected_keys=" << kNumKeys
<< ", data_mismatches=" << data_mismatches
<< ", iter_status=" << iter_status.ToString() << std::endl;
// Verify no data mismatches occurred for keys that were read
ASSERT_EQ(data_mismatches, 0)
<< "Found " << data_mismatches << " data mismatches";
if (read_async_called) {
// Async IO was used - verify Poll error was injected and propagated
ASSERT_EQ(poll_call_count.load(), 3)
<< "Expected exactly 3 Poll calls when error injected on 3rd call";
ASSERT_EQ(poll_error_injected_count.load(), 1)
<< "Expected exactly 1 Poll error to be injected";
// The iterator should have stopped with an error status
ASSERT_TRUE(iter_status.IsIOError())
<< "Expected iterator to report IOError after Poll failure, got: "
<< iter_status.ToString();
std::cout << "PollErrorRecoveryDuringIteration: Successfully verified "
"Poll error was injected and propagated to iterator"
<< std::endl;
} else {
// Async IO not supported - iterator should complete successfully
ASSERT_OK(iter_status);
ASSERT_EQ(keys_read, kNumKeys);
std::cout << "PollErrorRecoveryDuringIteration: Async IO (io_uring) not "
"supported on this platform, verified data correctness"
<< std::endl;
}
// Retry iteration without error injection - verify all data is still readable
// This confirms the Poll error didn't corrupt state
{
int retry_keys_read = 0;
int retry_data_mismatches = 0;
auto iter = std::unique_ptr<Iterator>(db_->NewIterator(ro));
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
std::string key = iter->key().ToString();
std::string value = iter->value().ToString();
auto it = expected_data.find(key);
if (it == expected_data.end()) {
retry_data_mismatches++;
} else if (it->second != value) {
retry_data_mismatches++;
}
retry_keys_read++;
}
ASSERT_OK(iter->status())
<< "Retry iteration failed: " << iter->status().ToString();
ASSERT_EQ(retry_keys_read, kNumKeys)
<< "Retry should read all " << kNumKeys << " keys";
ASSERT_EQ(retry_data_mismatches, 0)
<< "Retry found " << retry_data_mismatches << " data mismatches";
std::cout << "PollErrorRecoveryDuringIteration: Retry succeeded, read all "
<< retry_keys_read << " keys correctly" << std::endl;
}
Close();
}
namespace {
#ifdef GFLAGS
const int kMaxArgCount = 100;
@@ -3376,6 +3557,76 @@ TEST_F(FilePrefetchBufferTest, ForCompaction) {
0);
}
TEST_F(FilePrefetchBufferTest, PollErrorPropagation) {
// This test verifies that Poll() errors in PollIfNeeded are properly
// propagated rather than being silently ignored.
std::string fname = "poll-error-test";
Random rand(0);
std::string content = rand.RandomString(32768);
Write(fname, content);
FileOptions opts;
std::unique_ptr<RandomAccessFileReader> r;
Read(fname, opts, &r);
// Set up readahead params for async prefetching
ReadaheadParams readahead_params;
readahead_params.initial_readahead_size = 16384;
readahead_params.max_readahead_size = 16384;
FilePrefetchBuffer fpb(readahead_params, /*enable=*/true,
/*track_min_offset=*/false, fs());
Slice result;
// Start an async prefetch to set up async_read_in_progress_ state
Status s = fpb.PrefetchAsync(IOOptions(), r.get(), 0, 4096, &result);
// Skip test on platforms that don't support async IO
if (s.IsNotSupported()) {
ROCKSDB_GTEST_SKIP("Async IO not supported on this platform");
return;
}
ASSERT_TRUE(s.IsTryAgain());
std::cout << "PollErrorPropagation: Async IO supported, proceeding with test"
<< std::endl;
// Set up SyncPoint to inject Poll error
SyncPoint::GetInstance()->SetCallBack(
"FilePrefetchBuffer::PollIfNeeded:IOStatus", [&](void* arg) {
IOStatus* io_s = static_cast<IOStatus*>(arg);
*io_s = IOStatus::IOError("Injected Poll error for testing");
});
SyncPoint::GetInstance()->EnableProcessing();
// TryReadFromCache will call PollIfNeeded to complete the async read
IOOptions io_opts;
io_opts.rate_limiter_priority = Env::IOPriority::IO_LOW;
Status read_status;
bool found =
fpb.TryReadFromCache(io_opts, r.get(), 0, 4096, &result, &read_status);
// When PollIfNeeded fails:
// 1. PrefetchInternal returns the error status
// 2. TryReadFromCacheUntracked sets *status to the error and returns false
// Therefore: found should be false, and read_status should contain the error
ASSERT_FALSE(found) << "Expected TryReadFromCache to return false on Poll "
"error, but it returned true";
ASSERT_TRUE(read_status.IsIOError())
<< "Expected IOError status, got: " << read_status.ToString();
ASSERT_TRUE(read_status.ToString().find("Injected Poll error") !=
std::string::npos)
<< "Expected error message to contain 'Injected Poll error', got: "
<< read_status.ToString();
std::cout << "PollErrorPropagation: Poll error correctly propagated - "
<< "found=" << found << ", status=" << read_status.ToString()
<< std::endl;
SyncPoint::GetInstance()->DisableProcessing();
SyncPoint::GetInstance()->ClearAllCallBacks();
}
class FSBufferPrefetchTest
: public testing::Test,
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
+5
View File
@@ -22,7 +22,12 @@ namespace ROCKSDB_NAMESPACE {
// The unknown file checksum.
constexpr char kUnknownFileChecksum[] = "";
// The unknown sst file checksum function name.
// Indicates that the file metadata says that no checksum factory was configured
// when the file was written.
constexpr char kUnknownFileChecksumFuncName[] = "Unknown";
// Used when opening a file and there is no file checksum metadata to propagate
// at all.
constexpr char kNoFileChecksumFuncName[] = "Unavailable";
// The standard DB file checksum function name.
// This is the name of the checksum function returned by
// GetFileChecksumGenCrc32cFactory();
+16 -1
View File
@@ -201,6 +201,19 @@ struct FileOptions : EnvOptions {
// FSWritableFile object creation.
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET;
// File checksum of the file being opened. Empty string if no checksum is
// available.
std::string file_checksum;
// Name of the checksum function used to compute file_checksum. Set to
// kUnknownFileChecksumFuncName when file was created without a checksum
// factory. Set to kNoFileChecksumFuncName when no checksum metadata is
// available.
// Production FileSystems will accept empty values for both
// file_checksum and file_checksum_func_name, but internally within RocksDB
// that is forbidden for checking/auditing purposes.
std::string file_checksum_func_name;
FileOptions() : EnvOptions(), handoff_checksum_type(ChecksumType::kCRC32c) {}
FileOptions(const DBOptions& opts)
@@ -216,7 +229,9 @@ struct FileOptions : EnvOptions {
io_options(opts.io_options),
temperature(opts.temperature),
handoff_checksum_type(opts.handoff_checksum_type),
write_hint(opts.write_hint) {}
write_hint(opts.write_hint),
file_checksum(opts.file_checksum),
file_checksum_func_name(opts.file_checksum_func_name) {}
FileOptions& operator=(const FileOptions&) = default;
};
+1 -1
View File
@@ -13,7 +13,7 @@
// minor or major version number planned for release.
#define ROCKSDB_MAJOR 10
#define ROCKSDB_MINOR 11
#define ROCKSDB_PATCH 0
#define ROCKSDB_PATCH 2
// Make it easy to do conditional compilation based on version checks, i.e.
// #if ROCKSDB_VERSION_GE(4, 5, 6)
+1 -1
View File
@@ -66,7 +66,7 @@ IndexBuilder* IndexBuilder::CreateIndexBuilder(
break;
}
default: {
assert(!"Do not recognize the index type ");
assert(false && "Do not recognize the index type ");
break;
}
}
+3
View File
@@ -23,6 +23,7 @@
#include "port/port.h"
#include "rocksdb/db.h"
#include "rocksdb/env.h"
#include "rocksdb/file_checksum.h"
#include "rocksdb/iterator.h"
#include "rocksdb/slice_transform.h"
#include "rocksdb/status.h"
@@ -85,6 +86,7 @@ Status SstFileDumper::GetTableReader(const std::string& file_path) {
uint64_t file_size = 0;
FileOptions fopts = soptions_;
fopts.temperature = file_temp_;
fopts.file_checksum_func_name = kNoFileChecksumFuncName;
Status s = fs->NewRandomAccessFile(file_path, fopts, &file, nullptr);
if (s.ok()) {
// check empty file
@@ -129,6 +131,7 @@ Status SstFileDumper::GetTableReader(const std::string& file_path) {
if (magic_number == kCuckooTableMagicNumber) {
fopts = soptions_;
fopts.temperature = file_temp_;
fopts.file_checksum_func_name = kNoFileChecksumFuncName;
}
fs->NewRandomAccessFile(file_path, fopts, &file, nullptr);
+2
View File
@@ -11,6 +11,7 @@
#include "file/random_access_file_reader.h"
#include "options/cf_options.h"
#include "rocksdb/env.h"
#include "rocksdb/file_checksum.h"
#include "rocksdb/file_system.h"
#include "table/get_context.h"
#include "table/table_builder.h"
@@ -51,6 +52,7 @@ Status SstFileReader::Open(const std::string& file_path) {
std::unique_ptr<FSRandomAccessFile> file;
std::unique_ptr<RandomAccessFileReader> file_reader;
FileOptions fopts(r->soptions);
fopts.file_checksum_func_name = kNoFileChecksumFuncName;
const auto& fs = r->options.env->GetFileSystem();
s = fs->GetFileSize(file_path, fopts.io_options, &file_size, nullptr);
+18 -1
View File
@@ -223,6 +223,17 @@ compare_db()
set -e
}
compact_db()
{
set +e
[ "$SANITY_CHECK" ] || bash "$script_copy_dir"/compact_db.sh "$1" "$2" "$3"
if [ $? -ne 0 ]; then
echo ==== Error compacting DB at $1 ====
exit 1
fi
set -e
}
write_external_sst()
{
set +e
@@ -387,10 +398,16 @@ DISABLE_WARNING_AS_ERROR=1 invoke_make ldb -j$J
for checkout_ref in "${checkout_refs[@]}"
do
# We currently assume DB backward compatibility for every branch listed
# We assume DB backward compatibility for every branch listed
echo "== Use $current_checkout_name to open DB generated using $checkout_ref..."
compare_db $db_test_dir/$checkout_ref $current_db_test_dir db_dump.txt 1 0
echo "== Use $current_checkout_name to compact DB generated using $checkout_ref..."
compact_db $db_test_dir/$checkout_ref 1 0
echo "== After compaction, re-verify DB originally from $checkout_ref..."
compare_db $db_test_dir/$checkout_ref $current_db_test_dir db_dump_after_compact.txt 1 0
if member_of_array "$checkout_ref" "${ext_backward_only_refs[@]}" ||
member_of_array "$checkout_ref" "${ext_forward_refs[@]}"
then
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
#
# A shell script to compact DB generated by generate_random_db.sh.
# ./ldb needs to be available to be executed.
#
# Usage: <SCRIPT> <DB Path> [if_try_load_options] [if_ignore_unknown_options]
if [ "$#" -lt 1 ]; then
echo "usage: $BASH_SOURCE <db_directory> [if_try_load_options] [if_ignore_unknown_options]"
exit 1
fi
db_dir=$1
try_load_options=${2:-"1"}
ignore_unknown_options=${3:-"0"}
extra_params=
if [ "$try_load_options" = "0" ]; then
extra_params=" --try_load_options=false"
elif [ "$try_load_options" = "1" ]; then
extra_params=" --try_load_options=true"
fi
if [ "$ignore_unknown_options" = "1" ]; then
extra_params="$extra_params --ignore_unknown_options"
fi
set -e
echo == Compacting DB at $db_dir
./ldb compact --db=$db_dir $extra_params
+34
View File
@@ -29,6 +29,12 @@ if ./ldb --version 2>/dev/null >/dev/null; then
rm -rf $db_dir
fi
# Check if deleterange command is supported by grepping ldb --help
deleterange_support=
if ./ldb --help 2>&1 | grep -q deleterange; then
deleterange_support=1
fi
echo == Loading data from $input_data_dir to $db_dir
declare -a compression_opts=("no" "snappy" "zlib" "bzip2")
@@ -65,5 +71,33 @@ do
fi
./ldb load --db=$db_dir --compression_type=$c $d_arg --bloom_bits=10 \
--auto_compaction=false --create_if_missing < $input_data_dir/$f
# Use md5sum of file to deterministically decide whether to add a range
# tombstone (approximately 1/4 of files) and which key to delete
file_path=$input_data_dir/$f
hash=$(md5sum "$file_path" | cut -c1-8)
hash_int=$((16#$hash))
if [ $((hash_int % 4)) -eq 0 ]; then
# Pick a key from this file based on the hash
line_count=$(wc -l < "$file_path")
if [ "$line_count" -gt 0 ]; then
line_num=$((hash_int % line_count + 1))
key=$(sed -n "${line_num}p" "$file_path" | cut -d' ' -f1)
if [ -n "$key" ]; then
# Create end key by appending a character to make a small range
end_key="${key}0"
if [ "$deleterange_support" == "1" ]; then
echo "== Deleting range [$key, $end_key) from $f"
./ldb deleterange --db=$db_dir "$key" "$end_key"
else
# Fall back to point delete for equivalent logical contents
echo "== Deleting key $key from $f"
./ldb delete --db=$db_dir "$key"
fi
fi
fi
fi
let "n = n + 1"
done
@@ -1 +0,0 @@
* The new default for `BlockBasedTableOptions::format_version` is 7, which has been supported since RocksDB 10.4.0 and is required in order to use CompressionManagers supporting custom compression types.
@@ -1 +0,0 @@
* Fixed a small performance bug with `format_version=7` when decompressing formats other than Snappy and ZSTD.
@@ -1 +0,0 @@
Fixed an infinite compaction loop bug with User-Defined Timestamps (UDT) where bottommost files were repeatedly marked for compaction even though their timestamp could not be collapsed.
@@ -1 +0,0 @@
Bugfix for persisted UDT record sequence number zeroing logic.
@@ -1 +0,0 @@
* Remove remaining pieces of Lua integration
@@ -1 +0,0 @@
New SetOptions API that allows setting options for multiple CFs, avoiding the need to reserialize OPTIONS file for each CF
@@ -254,7 +254,7 @@ bool RandomAccessCacheFile::ParseRec(const LBA& lba, Slice* key, Slice* val,
CacheRecord rec;
if (!rec.Deserialize(data)) {
assert(!"Error deserializing data");
assert(false && "Error deserializing data");
Error(log_, "Error de-serializing record from file %s off %d",
Path().c_str(), lba.off_);
return false;
@@ -339,7 +339,7 @@ bool WriteableCacheFile::Append(const Slice& key, const Slice& val, LBA* lba) {
CacheRecord rec(key, val);
if (!rec.Serialize(&bufs_, &buf_woff_)) {
// unexpected error: unable to serialize the data
assert(!"Error serializing record");
assert(false && "Error serializing record");
return false;
}
@@ -101,14 +101,14 @@ class BlockCacheFile : public LRUElement<BlockCacheFile> {
// append key/value to file and return LBA locator to user
virtual bool Append(const Slice& /*key*/, const Slice& /*val*/,
LBA* const /*lba*/) {
assert(!"not implemented");
assert(false && "not implemented");
return false;
}
// read from the record locator (LBA) and return key, value and status
virtual bool Read(const LBA& /*lba*/, Slice* /*key*/, Slice* /*block*/,
char* /*scratch*/) {
assert(!"not implemented");
assert(false && "not implemented");
return false;
}
@@ -106,7 +106,7 @@ Status VolatileCacheTier::Lookup(const Slice& page_key,
}
bool VolatileCacheTier::Erase(const Slice& /*key*/) {
assert(!"not supported");
assert(false && "not supported");
return true;
}