mirror of
https://github.com/facebook/rocksdb.git
synced 2026-10-23 08:09:02 +03:00
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/15387 ## Summary RocksDB's `.clang-tidy` enables `cppcoreguidelines-special-member-functions`, `cppcoreguidelines-virtual-class-destructor` and the noexcept-move checks, and clang warns `-Wdeprecated-copy-with-user-provided-dtor`. A full scan reports 476 findings. In most of them, a class declares a destructor (or copy operations) but leaves the rest of copy/move implicit. That has two costs. Readers can't tell whether a class may be copied or moved. And resource-owning classes keep an implicit copy that would double-free if anyone ever used it. This diff resolves 453 of the 476. Approach, picked per class from least to most invasive: 1. An empty user-provided destructor becomes `= default`. This covers most interface classes, such as `virtual ~X() {}` -> `= default`. If the class is still copied, that would only rename the deprecated-copy warning, so the destructor is removed instead when nothing needs it (`Customizable`, `TableFactory` and `InternalKeyComparator` inherit a virtual one; `IteratorWrapperBase` isn't polymorphic), and `MemTableRep::KeyComparator` gets defaulted copy and move. 2. Classes whose copy is already deleted get explicitly deleted move operations too. Move was already unavailable, so this only documents it. 3. Resource-owning classes that are never copied get deleted copy and move operations. Examples: the C API wrapper structs, which run a `destructor_` callback (edited in `tools/c_api_gen/c_base.cc` and regenerated into `db/c.cc`); `ShardedCache`, which frees its shards; iterator wrappers; and RAII handles. 4. Value types that are copied or moved get the missing operations as `= default`, which keeps their current behavior. 5. Exception specifications of existing functions are unchanged, except for `WriteBatch`'s move assignment (below): no other `noexcept` is added or removed. Only the move operations this diff newly declares (`Comparator`, `FileSampledStats` and the thread-local `Entry`) are `noexcept`; they copy only scalars or atomics. `WriteBatch`'s copy and move assignment destroyed `*this` and placement-new'ed a new `WriteBatch` over it. That is undefined behavior when `*this` is a subclass, and if the copy constructor threw, `*this` was left destroyed and was later destroyed again. Move assignment now assigns member-wise and ends in the same state as the move constructor, which leaves `has_key_with_ts_`, the timestamp-size tracking and the other fields it doesn't copy at their defaults. `rep_` is swapped through a temporary, because a plain string move-assignment can keep the old buffer (e.g. a large batch reset with `batch = WriteBatch()` would keep its memory). Nothing in it can throw, so it is now `noexcept`. Copy assignment copies into a temporary and moves it in, so a throwing copy leaves `*this` unchanged. Bugs this exposed and fixed: - `RestoreAfterCopyOrCreateWorkItem` (backup engine): the hand-written move constructor and assignment moved only some fields, so `from_file` and `to_file` were empty after `push_back(std::move(...))`. A restore that hit a checksum mismatch then reported "While restoring -> : ..." without file names. It now uses the compiler-generated moves, which are still `noexcept`. - `ExternalSstFileIngestionJob` lived in a `std::vector`, and its destructor frees raw-owned pointers, so the implicit copy used on vector reallocation would double-free. Its file batches also point into its own inline file storage, so neither a copy nor a move is safe after `Prepare()`. Copy and move are now deleted, and `DBImpl` holds jobs through `std::unique_ptr`. Compatibility: no public class loses an operation users can call. Public interfaces mostly only get `virtual ~X() = default;`. `WriteBatch`'s move assignment becomes `noexcept`, which is not part of the mangled name, so the ABI doesn't change. `SubcompactionJobInfo` and `CompactionJobInfo` intentionally stay C++20 aggregates, so users' aggregate and designated initializers keep compiling. `Configurable`, `CompareInterface`, `WriteBatchBase`, `ObjectLibrary::Entry` and `MemTableRep::KeyComparator` declare defaulted copy and move, and `Customizable` no longer declares a destructor. As a result, `Configurable` and `Customizable` subclasses that declare no destructor or copy operations of their own now move their registered-options list instead of copying it. In-tree, these are the mutable-options adapters, the table factories, the memory allocators and test classes; all are held through smart pointers or used in place, and `Clone()` copies, so none is moved by value. Not changed (23): `IODebugContext` (custom deep copy plus a `std::shared_mutex`), `DBWithColumnFamilies` in db_bench (stored in a `std::vector` with non-movable members), `Snapshot`'s protected virtual destructor (intended), `ManagedSnapshot`, the `SubcompactionJobInfo` and `CompactionJobInfo` aggregates, and the 16 noexcept-move-constructor and noexcept-swap findings on existing functions, including `autovector`'s move assignment (adding `noexcept` would change their exception specifications). Copying `ManagedSnapshot` already double-releases the snapshot. Fixing that needs a real move implementation and is a behavior change, so it is left for a separate diff. Reviewed By: rban1 Differential Revision: D123186309 fbshipit-source-id: 6f8e57e233e12199a007407130bb9a1f6d52e8d1
780 lines
25 KiB
C++
780 lines
25 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.
|
|
//
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
|
|
#include "memory/memory_allocator_impl.h"
|
|
#include "rocksdb/advanced_compression.h"
|
|
#include "rocksdb/options.h"
|
|
#include "table/block_based/block_type.h"
|
|
#include "util/aligned_buffer.h"
|
|
#include "util/coding.h"
|
|
#include "util/compression_context_cache.h"
|
|
|
|
#ifdef ZSTD
|
|
#include <zstd.h>
|
|
#include <zstd_errors.h>
|
|
// ZSTD_Compress2(), ZSTD_compressStream2() and frame parameters all belong to
|
|
// advanced APIs and require v1.4.0+, which is from April 2019.
|
|
// https://github.com/facebook/zstd/blob/eb9f881eb810f2242f1ef36b3f3e7014eecb8fa6/lib/zstd.h#L297C40-L297C45
|
|
// To avoid a rat's nest of #ifdefs, we now require v1.4.0+ for ZSTD support.
|
|
#if ZSTD_VERSION_NUMBER < 10400
|
|
#error "ZSTD support requires version >= 1.4.0 (libzstd-devel)"
|
|
#endif // ZSTD_VERSION_NUMBER
|
|
// The above release also includes digested dictionary support, but some
|
|
// required functions (ZSTD_createDDict_byReference) are still only exported
|
|
// with ZSTD_STATIC_LINKING_ONLY defined.
|
|
#if defined(ZSTD_STATIC_LINKING_ONLY)
|
|
#define ROCKSDB_ZSTD_DDICT
|
|
#endif // defined(ZSTD_STATIC_LINKING_ONLY)
|
|
// For ZDICT_* functions
|
|
#include <zdict.h>
|
|
// ZDICT_finalizeDictionary API is exported and stable since v1.4.5
|
|
#if ZSTD_VERSION_NUMBER >= 10405
|
|
#define ROCKSDB_ZDICT_FINALIZE
|
|
#endif // ZSTD_VERSION_NUMBER >= 10405
|
|
#endif // ZSTD
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
// Need this for the context allocation override
|
|
// On windows we need to do this explicitly
|
|
#if defined(ZSTD) && defined(ROCKSDB_JEMALLOC) && defined(OS_WIN) && \
|
|
defined(ZSTD_STATIC_LINKING_ONLY)
|
|
#define ROCKSDB_ZSTD_CUSTOM_MEM
|
|
namespace port {
|
|
ZSTD_customMem GetJeZstdAllocationOverrides();
|
|
} // namespace port
|
|
#endif // defined(ZSTD) && defined(ROCKSDB_JEMALLOC) && defined(OS_WIN) &&
|
|
// defined(ZSTD_STATIC_LINKING_ONLY)
|
|
|
|
#ifdef ZSTD
|
|
// Translates a configured compression_opts.level into the effective ZSTD
|
|
// compression level. Besides resolving the "use default" sentinel, this
|
|
// removes a discontinuity at level 0: ZSTD itself treats a requested level of
|
|
// 0 as "use the default level" (historically 3), which would make level 0
|
|
// more aggressive than levels 1 and 2. Mapping 0 to -1 keeps the level spectrum
|
|
// monotonic, which is friendlier to auto-tuning.
|
|
inline int SanitizeZSTDCompressionLevel(int level) {
|
|
if (level == CompressionOptions::kDefaultCompressionLevel) {
|
|
// NB: ZSTD_CLEVEL_DEFAULT is historically == 3
|
|
return ZSTD_CLEVEL_DEFAULT;
|
|
} else if (level == 0) {
|
|
// Avoid library's discontinuity at level 0
|
|
return -1;
|
|
} else {
|
|
return level;
|
|
}
|
|
}
|
|
#endif // ZSTD
|
|
|
|
// Cached data represents a portion that can be re-used
|
|
// If, in the future we have more than one native context to
|
|
// cache we can arrange this as a tuple
|
|
class ZSTDUncompressCachedData {
|
|
public:
|
|
#if defined(ZSTD)
|
|
using ZSTDNativeContext = ZSTD_DCtx*;
|
|
#else
|
|
using ZSTDNativeContext = void*;
|
|
#endif // ZSTD
|
|
ZSTDUncompressCachedData() {}
|
|
// Init from cache
|
|
ZSTDUncompressCachedData(const ZSTDUncompressCachedData& o) = delete;
|
|
ZSTDUncompressCachedData& operator=(const ZSTDUncompressCachedData&) = delete;
|
|
ZSTDUncompressCachedData(ZSTDUncompressCachedData&& o) noexcept
|
|
: ZSTDUncompressCachedData() {
|
|
*this = std::move(o);
|
|
}
|
|
ZSTDUncompressCachedData& operator=(ZSTDUncompressCachedData&& o) noexcept {
|
|
assert(zstd_ctx_ == nullptr);
|
|
std::swap(zstd_ctx_, o.zstd_ctx_);
|
|
std::swap(cache_idx_, o.cache_idx_);
|
|
return *this;
|
|
}
|
|
ZSTDNativeContext Get() const { return zstd_ctx_; }
|
|
int64_t GetCacheIndex() const { return cache_idx_; }
|
|
void CreateIfNeeded() {
|
|
if (zstd_ctx_ == nullptr) {
|
|
#if !defined(ZSTD)
|
|
zstd_ctx_ = nullptr;
|
|
#elif defined(ROCKSDB_ZSTD_CUSTOM_MEM)
|
|
zstd_ctx_ =
|
|
ZSTD_createDCtx_advanced(port::GetJeZstdAllocationOverrides());
|
|
#else // ZSTD && !ROCKSDB_ZSTD_CUSTOM_MEM
|
|
zstd_ctx_ = ZSTD_createDCtx();
|
|
#endif
|
|
cache_idx_ = -1;
|
|
}
|
|
}
|
|
void InitFromCache(const ZSTDUncompressCachedData& o, int64_t idx) {
|
|
zstd_ctx_ = o.zstd_ctx_;
|
|
cache_idx_ = idx;
|
|
}
|
|
~ZSTDUncompressCachedData() {
|
|
#if defined(ZSTD)
|
|
if (zstd_ctx_ != nullptr && cache_idx_ == -1) {
|
|
ZSTD_freeDCtx(zstd_ctx_);
|
|
}
|
|
#endif // ZSTD
|
|
}
|
|
|
|
private:
|
|
ZSTDNativeContext zstd_ctx_ = nullptr;
|
|
int64_t cache_idx_ = -1; // -1 means this instance owns the context
|
|
};
|
|
} // namespace ROCKSDB_NAMESPACE
|
|
|
|
#if defined(XPRESS)
|
|
#include "port/xpress.h"
|
|
#endif
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
class FailureDecompressor : public Decompressor {
|
|
public:
|
|
explicit FailureDecompressor(Status&& status) : status_(std::move(status)) {
|
|
assert(!status_.ok());
|
|
}
|
|
~FailureDecompressor() override { status_.PermitUncheckedError(); }
|
|
// No copying allowed
|
|
FailureDecompressor(const FailureDecompressor&) = delete;
|
|
FailureDecompressor& operator=(const FailureDecompressor&) = delete;
|
|
FailureDecompressor(FailureDecompressor&&) = delete;
|
|
FailureDecompressor& operator=(FailureDecompressor&&) = delete;
|
|
|
|
const char* Name() const override { return "FailureDecompressor"; }
|
|
|
|
Status ExtractUncompressedSize(Args& /*args*/) override { return status_; }
|
|
|
|
Status DecompressBlock(const Args& /*args*/,
|
|
char* /*uncompressed_output*/) override {
|
|
return status_;
|
|
}
|
|
|
|
protected:
|
|
Status status_;
|
|
};
|
|
|
|
// Owns a decompression dictionary, and associated Decompressor, for storing
|
|
// in the block cache.
|
|
//
|
|
// Justification: for a "processed" dictionary to be saved in block cache, we
|
|
// also need a reference to the decompressor that processed it, to ensure it
|
|
// is recognized properly. At that point, we might as well have the dictionary
|
|
// part of the decompressor identity and track an associated decompressor along
|
|
// with a decompression dictionary in the block cache, and the decompressor
|
|
// hides potential details of processing the dictionary.
|
|
struct DecompressorDict {
|
|
// Block containing the data for the compression dictionary in case the
|
|
// constructor that takes a string parameter is used.
|
|
std::string dict_str_;
|
|
|
|
// Block containing the data for the compression dictionary in case the
|
|
// constructor that takes a Slice parameter is used and the passed in
|
|
// CacheAllocationPtr is not nullptr.
|
|
CacheAllocationPtr dict_allocation_;
|
|
|
|
// A Decompressor referencing and using the dictionary owned by this.
|
|
std::unique_ptr<Decompressor> decompressor_;
|
|
|
|
// Approximate owned memory usage
|
|
size_t memory_usage_;
|
|
|
|
DecompressorDict(std::string&& dict, Decompressor& from_decompressor)
|
|
: dict_str_(std::move(dict)) {
|
|
Populate(from_decompressor, dict_str_);
|
|
}
|
|
|
|
DecompressorDict(Slice slice, CacheAllocationPtr&& allocation,
|
|
Decompressor& from_decompressor)
|
|
: dict_allocation_(std::move(allocation)) {
|
|
Populate(from_decompressor, slice);
|
|
}
|
|
|
|
DecompressorDict(DecompressorDict&& rhs) noexcept
|
|
: dict_str_(std::move(rhs.dict_str_)),
|
|
dict_allocation_(std::move(rhs.dict_allocation_)),
|
|
decompressor_(std::move(rhs.decompressor_)),
|
|
memory_usage_(std::move(rhs.memory_usage_)) {}
|
|
|
|
DecompressorDict& operator=(DecompressorDict&& rhs) noexcept {
|
|
if (this == &rhs) {
|
|
return *this;
|
|
}
|
|
dict_str_ = std::move(rhs.dict_str_);
|
|
dict_allocation_ = std::move(rhs.dict_allocation_);
|
|
decompressor_ = std::move(rhs.decompressor_);
|
|
return *this;
|
|
}
|
|
// Disable copy
|
|
DecompressorDict(const DecompressorDict&) = delete;
|
|
DecompressorDict& operator=(const DecompressorDict&) = delete;
|
|
~DecompressorDict() = default;
|
|
|
|
// The object is self-contained if the string constructor is used, or the
|
|
// Slice constructor is invoked with a non-null allocation. Otherwise, it
|
|
// is the caller's responsibility to ensure that the underlying storage
|
|
// outlives this object.
|
|
bool own_bytes() const { return !dict_str_.empty() || dict_allocation_; }
|
|
|
|
const Slice& GetRawDict() const { return decompressor_->GetSerializedDict(); }
|
|
|
|
// For TypedCacheInterface
|
|
const Slice& ContentSlice() const { return GetRawDict(); }
|
|
static constexpr CacheEntryRole kCacheEntryRole = CacheEntryRole::kOtherBlock;
|
|
static constexpr BlockType kBlockType = BlockType::kCompressionDictionary;
|
|
|
|
size_t ApproximateMemoryUsage() const { return memory_usage_; }
|
|
|
|
private:
|
|
void Populate(Decompressor& from_decompressor, Slice dict);
|
|
};
|
|
|
|
// Holds dictionary and related data, like ZSTD's digested compression
|
|
// dictionary.
|
|
struct CompressionDict {
|
|
#ifdef ZSTD
|
|
ZSTD_CDict* zstd_cdict_ = nullptr;
|
|
#endif // ZSTD
|
|
std::string dict_;
|
|
|
|
public:
|
|
CompressionDict() = default;
|
|
CompressionDict(std::string&& dict, CompressionType type, int level) {
|
|
dict_ = std::move(dict);
|
|
#ifdef ZSTD
|
|
zstd_cdict_ = nullptr;
|
|
if (!dict_.empty() && type == kZSTD) {
|
|
level = SanitizeZSTDCompressionLevel(level);
|
|
// Should be safe (but slower) if below call fails as we'll use the
|
|
// raw dictionary to compress.
|
|
zstd_cdict_ = ZSTD_createCDict(dict_.data(), dict_.size(), level);
|
|
assert(zstd_cdict_ != nullptr);
|
|
}
|
|
#else
|
|
(void)type;
|
|
(void)level;
|
|
#endif // ZSTD
|
|
}
|
|
|
|
CompressionDict(CompressionDict&& other) {
|
|
#ifdef ZSTD
|
|
zstd_cdict_ = other.zstd_cdict_;
|
|
other.zstd_cdict_ = nullptr;
|
|
#endif // ZSTD
|
|
dict_ = std::move(other.dict_);
|
|
}
|
|
CompressionDict& operator=(CompressionDict&& other) {
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
#ifdef ZSTD
|
|
zstd_cdict_ = other.zstd_cdict_;
|
|
other.zstd_cdict_ = nullptr;
|
|
#endif // ZSTD
|
|
dict_ = std::move(other.dict_);
|
|
return *this;
|
|
}
|
|
|
|
~CompressionDict() {
|
|
#ifdef ZSTD
|
|
size_t res = 0;
|
|
if (zstd_cdict_ != nullptr) {
|
|
res = ZSTD_freeCDict(zstd_cdict_);
|
|
}
|
|
assert(res == 0); // Last I checked they can't fail
|
|
(void)res; // prevent unused var warning
|
|
#endif // ZSTD
|
|
}
|
|
|
|
#ifdef ZSTD
|
|
const ZSTD_CDict* GetDigestedZstdCDict() const { return zstd_cdict_; }
|
|
#endif // ZSTD
|
|
|
|
Slice GetRawDict() const { return dict_; }
|
|
bool empty() const { return dict_.empty(); }
|
|
|
|
static const CompressionDict& GetEmptyDict() {
|
|
static CompressionDict empty_dict{};
|
|
return empty_dict;
|
|
}
|
|
|
|
// Disable copy
|
|
CompressionDict(const CompressionDict&) = delete;
|
|
CompressionDict& operator=(const CompressionDict&) = delete;
|
|
};
|
|
|
|
class CompressionContext : public Compressor::WorkingArea {
|
|
private:
|
|
#ifdef ZSTD
|
|
ZSTD_CCtx* zstd_ctx_ = nullptr;
|
|
|
|
ZSTD_CCtx* CreateZSTDContext() {
|
|
#ifdef ROCKSDB_ZSTD_CUSTOM_MEM
|
|
return ZSTD_createCCtx_advanced(port::GetJeZstdAllocationOverrides());
|
|
#else // ROCKSDB_ZSTD_CUSTOM_MEM
|
|
return ZSTD_createCCtx();
|
|
#endif // ROCKSDB_ZSTD_CUSTOM_MEM
|
|
}
|
|
|
|
public:
|
|
// callable inside ZSTD_Compress
|
|
ZSTD_CCtx* ZSTDPreallocCtx() const {
|
|
assert(zstd_ctx_ != nullptr);
|
|
return zstd_ctx_;
|
|
}
|
|
|
|
private:
|
|
#endif // ZSTD
|
|
|
|
void CreateNativeContext(CompressionType type, int level, bool checksum) {
|
|
#ifdef ZSTD
|
|
if (type == kZSTD) {
|
|
zstd_ctx_ = CreateZSTDContext();
|
|
level = SanitizeZSTDCompressionLevel(level);
|
|
size_t err =
|
|
ZSTD_CCtx_setParameter(zstd_ctx_, ZSTD_c_compressionLevel, level);
|
|
if (ZSTD_isError(err)) {
|
|
assert(false);
|
|
ZSTD_freeCCtx(zstd_ctx_);
|
|
zstd_ctx_ = CreateZSTDContext();
|
|
}
|
|
if (checksum) {
|
|
err = ZSTD_CCtx_setParameter(zstd_ctx_, ZSTD_c_checksumFlag, 1);
|
|
if (ZSTD_isError(err)) {
|
|
assert(false);
|
|
ZSTD_freeCCtx(zstd_ctx_);
|
|
zstd_ctx_ = CreateZSTDContext();
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
(void)type;
|
|
(void)level;
|
|
(void)checksum;
|
|
#endif // ZSTD
|
|
}
|
|
void DestroyNativeContext() {
|
|
#ifdef ZSTD
|
|
if (zstd_ctx_ != nullptr) {
|
|
ZSTD_freeCCtx(zstd_ctx_);
|
|
}
|
|
#endif // ZSTD
|
|
}
|
|
|
|
public:
|
|
explicit CompressionContext(CompressionType type,
|
|
const CompressionOptions& options) {
|
|
CreateNativeContext(type, options.level, options.checksum);
|
|
}
|
|
~CompressionContext() { DestroyNativeContext(); }
|
|
CompressionContext(const CompressionContext&) = delete;
|
|
CompressionContext& operator=(const CompressionContext&) = delete;
|
|
CompressionContext(CompressionContext&&) = delete;
|
|
CompressionContext& operator=(CompressionContext&&) = delete;
|
|
};
|
|
|
|
// This is like a working area, reusable for different dicts, etc.
|
|
// TODO: refactor / consolidate
|
|
class UncompressionContext : public Decompressor::WorkingArea {
|
|
private:
|
|
CompressionContextCache* ctx_cache_ = nullptr;
|
|
ZSTDUncompressCachedData uncomp_cached_data_;
|
|
|
|
public:
|
|
explicit UncompressionContext(CompressionType type) {
|
|
if (type == kZSTD) {
|
|
ctx_cache_ = CompressionContextCache::Instance();
|
|
uncomp_cached_data_ = ctx_cache_->GetCachedZSTDUncompressData();
|
|
}
|
|
}
|
|
~UncompressionContext() {
|
|
if (uncomp_cached_data_.GetCacheIndex() != -1) {
|
|
assert(ctx_cache_ != nullptr);
|
|
ctx_cache_->ReturnCachedZSTDUncompressData(
|
|
uncomp_cached_data_.GetCacheIndex());
|
|
}
|
|
}
|
|
UncompressionContext(const UncompressionContext&) = delete;
|
|
UncompressionContext& operator=(const UncompressionContext&) = delete;
|
|
UncompressionContext(UncompressionContext&&) = delete;
|
|
UncompressionContext& operator=(UncompressionContext&&) = delete;
|
|
|
|
ZSTDUncompressCachedData::ZSTDNativeContext GetZSTDContext() const {
|
|
return uncomp_cached_data_.Get();
|
|
}
|
|
};
|
|
|
|
inline bool Snappy_Supported() {
|
|
#ifdef SNAPPY
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline bool Zlib_Supported() {
|
|
#ifdef ZLIB
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline bool BZip2_Supported() {
|
|
#ifdef BZIP2
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline bool LZ4_Supported() {
|
|
#ifdef LZ4
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline CompressionType GetDefaultCompressionType() {
|
|
return LZ4_Supported()
|
|
? kLZ4Compression
|
|
: (Snappy_Supported() ? kSnappyCompression : kNoCompression);
|
|
}
|
|
|
|
inline bool XPRESS_Supported() {
|
|
#ifdef XPRESS
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline bool ZSTD_Supported() {
|
|
#ifdef ZSTD
|
|
// NB: ZSTD format is finalized since version 0.8.0. See ZSTD_VERSION_NUMBER
|
|
// check above.
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline bool ZSTD_Streaming_Supported() {
|
|
#if defined(ZSTD)
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline bool StreamingCompressionTypeSupported(
|
|
CompressionType compression_type) {
|
|
switch (compression_type) {
|
|
case kNoCompression:
|
|
return true;
|
|
case kZSTD:
|
|
return ZSTD_Streaming_Supported();
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline bool CompressionTypeSupported(CompressionType compression_type) {
|
|
switch (compression_type) {
|
|
case kNoCompression:
|
|
return true;
|
|
case kSnappyCompression:
|
|
return Snappy_Supported();
|
|
case kZlibCompression:
|
|
return Zlib_Supported();
|
|
case kBZip2Compression:
|
|
return BZip2_Supported();
|
|
case kLZ4Compression:
|
|
return LZ4_Supported();
|
|
case kLZ4HCCompression:
|
|
return LZ4_Supported();
|
|
case kXpressCompression:
|
|
return XPRESS_Supported();
|
|
case kZSTD:
|
|
return ZSTD_Supported();
|
|
default: // Including custom compression types
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline bool DictCompressionTypeSupported(CompressionType compression_type) {
|
|
switch (compression_type) {
|
|
case kNoCompression:
|
|
return false;
|
|
case kSnappyCompression:
|
|
return false;
|
|
case kZlibCompression:
|
|
return Zlib_Supported();
|
|
case kBZip2Compression:
|
|
return false;
|
|
case kLZ4Compression:
|
|
case kLZ4HCCompression:
|
|
#if LZ4_VERSION_NUMBER >= 10400 // r124+
|
|
return LZ4_Supported();
|
|
#else
|
|
return false;
|
|
#endif
|
|
case kXpressCompression:
|
|
return false;
|
|
case kZSTD:
|
|
// NB: dictionary supported since 0.5.0. See ZSTD_VERSION_NUMBER check
|
|
// above.
|
|
return ZSTD_Supported();
|
|
default: // Including custom compression types
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// WART: does not match OptionsHelper::compression_type_string_map
|
|
std::string CompressionTypeToString(CompressionType compression_type);
|
|
|
|
// WART: does not match OptionsHelper::compression_type_string_map
|
|
CompressionType CompressionTypeFromString(std::string compression_type_str);
|
|
|
|
std::string CompressionOptionsToString(
|
|
const CompressionOptions& compression_options);
|
|
|
|
inline bool ZSTD_TrainDictionarySupported() {
|
|
#ifdef ZSTD
|
|
// NB: Dictionary trainer is available since v0.6.1 for static linking, but
|
|
// not available for dynamic linking until v1.1.3. See ZSTD_VERSION_NUMBER
|
|
// check above.
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
inline bool ZSTD_FinalizeDictionarySupported() {
|
|
#ifdef ROCKSDB_ZDICT_FINALIZE
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
// Use to check whether compression types are related or unrelated
|
|
inline CompressionType CanonicalCompressionType(CompressionType type) {
|
|
switch (type) {
|
|
// Configuring LZ4 or LZ4HC can result in using the other, depending on
|
|
// compression level.
|
|
case kLZ4HCCompression:
|
|
return kLZ4Compression;
|
|
default:
|
|
return type;
|
|
}
|
|
}
|
|
|
|
// The new compression APIs intentionally make it difficult to generate
|
|
// compressed data larger than the original. (It is better to store the
|
|
// uncompressed version in that case.) For legacy cases that must store
|
|
// compressed data even when larger than the uncompressed, this is a convenient
|
|
// wrapper to support that, with a compressor from BuiltinCompressionManager and
|
|
// a GrowableBuffer.
|
|
Status LegacyForceBuiltinCompression(
|
|
Compressor& builtin_compressor,
|
|
Compressor::ManagedWorkingArea* working_area, Slice from,
|
|
GrowableBuffer* to);
|
|
|
|
// Records the compression type for subsequent WAL records.
|
|
class CompressionTypeRecord {
|
|
public:
|
|
explicit CompressionTypeRecord(CompressionType compression_type)
|
|
: compression_type_(compression_type) {}
|
|
|
|
CompressionType GetCompressionType() const { return compression_type_; }
|
|
|
|
inline void EncodeTo(std::string* dst) const {
|
|
assert(dst != nullptr);
|
|
PutFixed32(dst, compression_type_);
|
|
}
|
|
|
|
inline Status DecodeFrom(Slice* src) {
|
|
constexpr char class_name[] = "CompressionTypeRecord";
|
|
|
|
uint32_t val;
|
|
if (!GetFixed32(src, &val)) {
|
|
return Status::Corruption(class_name,
|
|
"Error decoding WAL compression type");
|
|
}
|
|
CompressionType compression_type = static_cast<CompressionType>(val);
|
|
if (!StreamingCompressionTypeSupported(compression_type)) {
|
|
return Status::Corruption(class_name,
|
|
"WAL compression type not supported");
|
|
}
|
|
compression_type_ = compression_type;
|
|
return Status::OK();
|
|
}
|
|
|
|
inline std::string DebugString() const {
|
|
return "compression_type: " + CompressionTypeToString(compression_type_);
|
|
}
|
|
|
|
private:
|
|
CompressionType compression_type_;
|
|
};
|
|
|
|
// Base class to implement compression for a stream of buffers.
|
|
// Instantiate an implementation of the class using Create() with the
|
|
// compression type and use Compress() repeatedly.
|
|
// The output buffer needs to be at least max_output_len.
|
|
// Call Reset() in between frame boundaries or in case of an error.
|
|
// NOTE: This class is not thread safe.
|
|
class StreamingCompress {
|
|
public:
|
|
StreamingCompress(CompressionType compression_type,
|
|
const CompressionOptions& opts,
|
|
uint32_t compress_format_version, size_t max_output_len)
|
|
: compression_type_(compression_type),
|
|
opts_(opts),
|
|
compress_format_version_(compress_format_version),
|
|
max_output_len_(max_output_len) {}
|
|
virtual ~StreamingCompress() = default;
|
|
// compress should be called repeatedly with the same input till the method
|
|
// returns 0
|
|
// Parameters:
|
|
// input - buffer to compress
|
|
// input_size - size of input buffer
|
|
// output - compressed buffer allocated by caller, should be at least
|
|
// max_output_len
|
|
// output_size - size of the output buffer
|
|
// Returns -1 for errors, the remaining size of the input buffer that needs
|
|
// to be compressed
|
|
virtual int Compress(const char* input, size_t input_size, char* output,
|
|
size_t* output_pos) = 0;
|
|
// static method to create object of a class inherited from
|
|
// StreamingCompress based on the actual compression type.
|
|
static std::unique_ptr<StreamingCompress> Create(
|
|
CompressionType compression_type, const CompressionOptions& opts,
|
|
uint32_t compress_format_version, size_t max_output_len);
|
|
virtual void Reset() = 0;
|
|
|
|
protected:
|
|
const CompressionType compression_type_;
|
|
const CompressionOptions opts_;
|
|
const uint32_t compress_format_version_;
|
|
const size_t max_output_len_;
|
|
};
|
|
|
|
// Base class to uncompress a stream of compressed buffers.
|
|
// Instantiate an implementation of the class using Create() with the
|
|
// compression type and use Uncompress() repeatedly.
|
|
// The output buffer needs to be at least max_output_len.
|
|
// Call Reset() in between frame boundaries or in case of an error.
|
|
// NOTE: This class is not thread safe.
|
|
class StreamingUncompress {
|
|
public:
|
|
StreamingUncompress(CompressionType compression_type,
|
|
uint32_t compress_format_version, size_t max_output_len)
|
|
: compression_type_(compression_type),
|
|
compress_format_version_(compress_format_version),
|
|
max_output_len_(max_output_len) {}
|
|
virtual ~StreamingUncompress() = default;
|
|
// Uncompress can be called repeatedly to progressively process the same
|
|
// input buffer, or can be called with a new input buffer. When the input
|
|
// buffer is not fully consumed, the return value is > 0 or output_size
|
|
// == max_output_len. When calling uncompress to continue processing the
|
|
// same input buffer, the input argument should be nullptr.
|
|
// Parameters:
|
|
// input - buffer to uncompress
|
|
// input_size - size of input buffer
|
|
// output - uncompressed buffer allocated by caller, should be at least
|
|
// max_output_len
|
|
// output_size - size of the output buffer
|
|
// Returns -1 for errors, remaining input to be processed otherwise.
|
|
virtual int Uncompress(const char* input, size_t input_size, char* output,
|
|
size_t* output_pos) = 0;
|
|
static std::unique_ptr<StreamingUncompress> Create(
|
|
CompressionType compression_type, uint32_t compress_format_version,
|
|
size_t max_output_len);
|
|
virtual void Reset() = 0;
|
|
|
|
protected:
|
|
CompressionType compression_type_;
|
|
uint32_t compress_format_version_;
|
|
size_t max_output_len_;
|
|
};
|
|
|
|
class ZSTDStreamingCompress final : public StreamingCompress {
|
|
public:
|
|
explicit ZSTDStreamingCompress(const CompressionOptions& opts,
|
|
uint32_t compress_format_version,
|
|
size_t max_output_len)
|
|
: StreamingCompress(kZSTD, opts, compress_format_version,
|
|
max_output_len) {
|
|
#ifdef ZSTD
|
|
cctx_ = ZSTD_createCCtx();
|
|
// Each compressed frame will have a checksum
|
|
ZSTD_CCtx_setParameter(cctx_, ZSTD_c_checksumFlag, 1);
|
|
assert(cctx_ != nullptr);
|
|
input_buffer_ = {/*src=*/nullptr, /*size=*/0, /*pos=*/0};
|
|
#endif
|
|
}
|
|
~ZSTDStreamingCompress() override {
|
|
#ifdef ZSTD
|
|
ZSTD_freeCCtx(cctx_);
|
|
#endif
|
|
}
|
|
// No copying allowed
|
|
ZSTDStreamingCompress(const ZSTDStreamingCompress&) = delete;
|
|
ZSTDStreamingCompress& operator=(const ZSTDStreamingCompress&) = delete;
|
|
ZSTDStreamingCompress(ZSTDStreamingCompress&&) = delete;
|
|
ZSTDStreamingCompress& operator=(ZSTDStreamingCompress&&) = delete;
|
|
int Compress(const char* input, size_t input_size, char* output,
|
|
size_t* output_pos) override;
|
|
void Reset() override;
|
|
#ifdef ZSTD
|
|
ZSTD_CCtx* cctx_;
|
|
ZSTD_inBuffer input_buffer_;
|
|
#endif
|
|
};
|
|
|
|
class ZSTDStreamingUncompress final : public StreamingUncompress {
|
|
public:
|
|
explicit ZSTDStreamingUncompress(uint32_t compress_format_version,
|
|
size_t max_output_len)
|
|
: StreamingUncompress(kZSTD, compress_format_version, max_output_len) {
|
|
#ifdef ZSTD
|
|
dctx_ = ZSTD_createDCtx();
|
|
assert(dctx_ != nullptr);
|
|
input_buffer_ = {/*src=*/nullptr, /*size=*/0, /*pos=*/0};
|
|
#endif
|
|
}
|
|
~ZSTDStreamingUncompress() override {
|
|
#ifdef ZSTD
|
|
ZSTD_freeDCtx(dctx_);
|
|
#endif
|
|
}
|
|
// No copying allowed
|
|
ZSTDStreamingUncompress(const ZSTDStreamingUncompress&) = delete;
|
|
ZSTDStreamingUncompress& operator=(const ZSTDStreamingUncompress&) = delete;
|
|
ZSTDStreamingUncompress(ZSTDStreamingUncompress&&) = delete;
|
|
ZSTDStreamingUncompress& operator=(ZSTDStreamingUncompress&&) = delete;
|
|
int Uncompress(const char* input, size_t input_size, char* output,
|
|
size_t* output_size) override;
|
|
void Reset() override;
|
|
|
|
private:
|
|
#ifdef ZSTD
|
|
ZSTD_DCtx* dctx_;
|
|
ZSTD_inBuffer input_buffer_;
|
|
#endif
|
|
};
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|