Summary: Fix a longstanding race condition in SetOptions for `block_based_table_factory` options. The fix is mostly described in new, unified `TableFactoryParseFn()` in `cf_options.cc`. Also in this PR: * Adds a virtual `Clone()` function to TableFactory * To avoid behavioral hiccups with `SetOptions`, make the "hidden state" of `BlockBasedTableFactory` shared between an original and a clone. For example, `TailPrefetchStats` * `Configurable` was allowed to be copied but was not safe to do so, because the copy would have and use pointers into object it was copied from (!!!). This has been fixed using relative instead of absolute pointers, though it's still technically relying on undefined behavior (consistent object layout for non-standard-layout types). For future follow-up: * Deny SetOptions on block cache options (dubious and not yet made safe with proper shared_ptr handling) Fixes https://github.com/facebook/rocksdb/issues/10079 Pull Request resolved: https://github.com/facebook/rocksdb/pull/13082 Test Plan: added to unit tests and crash test Ran TSAN blackbox crashtest for hours with options to amplify potential race (see https://github.com/facebook/rocksdb/issues/10079) Reviewed By: cbi42 Differential Revision: D64947243 Pulled By: pdillinger fbshipit-source-id: 8390299149f50e2a2b39a5247680f2637edb23c8
734 lines
27 KiB
C++
734 lines
27 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).
|
|
|
|
#include "rocksdb/configurable.h"
|
|
|
|
#include "logging/logging.h"
|
|
#include "options/configurable_helper.h"
|
|
#include "options/options_helper.h"
|
|
#include "rocksdb/customizable.h"
|
|
#include "rocksdb/status.h"
|
|
#include "rocksdb/utilities/object_registry.h"
|
|
#include "rocksdb/utilities/options_type.h"
|
|
#include "util/coding.h"
|
|
#include "util/string_util.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
namespace {
|
|
intptr_t GetOffset(const Configurable* holder, void* field) {
|
|
return reinterpret_cast<intptr_t>(field) -
|
|
reinterpret_cast<intptr_t>(static_cast<const void*>(holder));
|
|
}
|
|
|
|
void* ApplyOffset(const Configurable* holder, intptr_t offset) {
|
|
return reinterpret_cast<void*>(
|
|
reinterpret_cast<intptr_t>(static_cast<const void*>(holder)) + offset);
|
|
}
|
|
} // namespace
|
|
|
|
void Configurable::RegisterOptions(
|
|
const std::string& name, void* opt_ptr,
|
|
const std::unordered_map<std::string, OptionTypeInfo>* type_map) {
|
|
RegisteredOptions opts;
|
|
opts.name = name;
|
|
opts.type_map = type_map;
|
|
opts.opt_offset = GetOffset(this, opt_ptr);
|
|
options_.emplace_back(opts);
|
|
}
|
|
|
|
//*************************************************************************
|
|
//
|
|
// Methods for Initializing and Validating Configurable Objects
|
|
//
|
|
//*************************************************************************
|
|
|
|
Status Configurable::PrepareOptions(const ConfigOptions& opts) {
|
|
// We ignore the invoke_prepare_options here intentionally,
|
|
// as if you are here, you must have called PrepareOptions explicitly.
|
|
Status status = Status::OK();
|
|
for (const auto& opt_iter : options_) {
|
|
if (opt_iter.type_map != nullptr) {
|
|
for (const auto& map_iter : *(opt_iter.type_map)) {
|
|
auto& opt_info = map_iter.second;
|
|
if (opt_info.ShouldPrepare()) {
|
|
status = opt_info.Prepare(opts, map_iter.first,
|
|
ApplyOffset(this, opt_iter.opt_offset));
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
Status Configurable::ValidateOptions(const DBOptions& db_opts,
|
|
const ColumnFamilyOptions& cf_opts) const {
|
|
Status status;
|
|
for (const auto& opt_iter : options_) {
|
|
if (opt_iter.type_map != nullptr) {
|
|
for (const auto& map_iter : *(opt_iter.type_map)) {
|
|
auto& opt_info = map_iter.second;
|
|
if (opt_info.ShouldValidate()) {
|
|
status = opt_info.Validate(db_opts, cf_opts, map_iter.first,
|
|
ApplyOffset(this, opt_iter.opt_offset));
|
|
if (!status.ok()) {
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/*********************************************************************************/
|
|
/* */
|
|
/* Methods for Retrieving Options from Configurables */
|
|
/* */
|
|
/*********************************************************************************/
|
|
|
|
const void* Configurable::GetOptionsPtr(const std::string& name) const {
|
|
for (const auto& o : options_) {
|
|
if (o.name == name) {
|
|
return ApplyOffset(this, o.opt_offset);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::string Configurable::GetOptionName(const std::string& opt_name) const {
|
|
return opt_name;
|
|
}
|
|
|
|
const OptionTypeInfo* ConfigurableHelper::FindOption(
|
|
const Configurable& configurable, const std::string& short_name,
|
|
std::string* opt_name, void** opt_ptr) {
|
|
for (const auto& iter : configurable.options_) {
|
|
if (iter.type_map != nullptr) {
|
|
const auto opt_info =
|
|
OptionTypeInfo::Find(short_name, *(iter.type_map), opt_name);
|
|
if (opt_info != nullptr) {
|
|
*opt_ptr = ApplyOffset(&configurable, iter.opt_offset);
|
|
return opt_info;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//*************************************************************************
|
|
//
|
|
// Methods for Configuring Options from Strings/Name-Value Pairs/Maps
|
|
//
|
|
//*************************************************************************
|
|
|
|
Status Configurable::ConfigureFromMap(
|
|
const ConfigOptions& config_options,
|
|
const std::unordered_map<std::string, std::string>& opts_map) {
|
|
Status s = ConfigureFromMap(config_options, opts_map, nullptr);
|
|
return s;
|
|
}
|
|
|
|
Status Configurable::ConfigureFromMap(
|
|
const ConfigOptions& config_options,
|
|
const std::unordered_map<std::string, std::string>& opts_map,
|
|
std::unordered_map<std::string, std::string>* unused) {
|
|
return ConfigureOptions(config_options, opts_map, unused);
|
|
}
|
|
|
|
Status Configurable::ConfigureOptions(
|
|
const ConfigOptions& config_options,
|
|
const std::unordered_map<std::string, std::string>& opts_map,
|
|
std::unordered_map<std::string, std::string>* unused) {
|
|
std::string curr_opts;
|
|
Status s;
|
|
if (!opts_map.empty()) {
|
|
// There are options in the map.
|
|
// Save the current configuration in curr_opts and then configure the
|
|
// options, but do not prepare them now. We will do all the prepare when
|
|
// the configuration is complete.
|
|
ConfigOptions copy = config_options;
|
|
copy.invoke_prepare_options = false;
|
|
if (!config_options.ignore_unknown_options) {
|
|
// If we are not ignoring unused, get the defaults in case we need to
|
|
// reset
|
|
copy.depth = ConfigOptions::kDepthDetailed;
|
|
copy.delimiter = "; ";
|
|
GetOptionString(copy, &curr_opts).PermitUncheckedError();
|
|
}
|
|
|
|
s = ConfigurableHelper::ConfigureOptions(copy, *this, opts_map, unused);
|
|
}
|
|
if (config_options.invoke_prepare_options && s.ok()) {
|
|
s = PrepareOptions(config_options);
|
|
}
|
|
if (!s.ok() && !curr_opts.empty()) {
|
|
ConfigOptions reset = config_options;
|
|
reset.ignore_unknown_options = true;
|
|
reset.invoke_prepare_options = true;
|
|
reset.ignore_unsupported_options = true;
|
|
// There are some options to reset from this current error
|
|
ConfigureFromString(reset, curr_opts).PermitUncheckedError();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
Status Configurable::ParseStringOptions(const ConfigOptions& /*config_options*/,
|
|
const std::string& /*opts_str*/) {
|
|
return Status::OK();
|
|
}
|
|
|
|
Status Configurable::ConfigureFromString(const ConfigOptions& config_options,
|
|
const std::string& opts_str) {
|
|
Status s;
|
|
if (!opts_str.empty()) {
|
|
if (opts_str.find(';') != std::string::npos ||
|
|
opts_str.find('=') != std::string::npos) {
|
|
std::unordered_map<std::string, std::string> opt_map;
|
|
s = StringToMap(opts_str, &opt_map);
|
|
if (s.ok()) {
|
|
s = ConfigureFromMap(config_options, opt_map, nullptr);
|
|
}
|
|
} else {
|
|
s = ParseStringOptions(config_options, opts_str);
|
|
if (s.ok() && config_options.invoke_prepare_options) {
|
|
s = PrepareOptions(config_options);
|
|
}
|
|
}
|
|
} else if (config_options.invoke_prepare_options) {
|
|
s = PrepareOptions(config_options);
|
|
} else {
|
|
s = Status::OK();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Sets the value of the named property to the input value, returning OK on
|
|
* succcess.
|
|
*/
|
|
Status Configurable::ConfigureOption(const ConfigOptions& config_options,
|
|
const std::string& name,
|
|
const std::string& value) {
|
|
return ConfigurableHelper::ConfigureSingleOption(config_options, *this, name,
|
|
value);
|
|
}
|
|
|
|
/**
|
|
* Looks for the named option amongst the options for this type and sets
|
|
* the value for it to be the input value.
|
|
* If the name was found, found_option will be set to true and the resulting
|
|
* status should be returned.
|
|
*/
|
|
|
|
Status Configurable::ParseOption(const ConfigOptions& config_options,
|
|
const OptionTypeInfo& opt_info,
|
|
const std::string& opt_name,
|
|
const std::string& opt_value, void* opt_ptr) {
|
|
if (opt_info.IsMutable()) {
|
|
if (config_options.mutable_options_only) {
|
|
// This option is mutable. Treat all of its children as mutable as well
|
|
ConfigOptions copy = config_options;
|
|
copy.mutable_options_only = false;
|
|
return opt_info.Parse(copy, opt_name, opt_value, opt_ptr);
|
|
} else {
|
|
return opt_info.Parse(config_options, opt_name, opt_value, opt_ptr);
|
|
}
|
|
} else if (config_options.mutable_options_only) {
|
|
return Status::InvalidArgument("Option not changeable: " + opt_name);
|
|
} else {
|
|
return opt_info.Parse(config_options, opt_name, opt_value, opt_ptr);
|
|
}
|
|
}
|
|
|
|
|
|
Status ConfigurableHelper::ConfigureOptions(
|
|
const ConfigOptions& config_options, Configurable& configurable,
|
|
const std::unordered_map<std::string, std::string>& opts_map,
|
|
std::unordered_map<std::string, std::string>* unused) {
|
|
std::unordered_map<std::string, std::string> remaining = opts_map;
|
|
Status s = Status::OK();
|
|
if (!opts_map.empty()) {
|
|
for (const auto& iter : configurable.options_) {
|
|
if (iter.type_map != nullptr) {
|
|
s = ConfigureSomeOptions(config_options, configurable, *(iter.type_map),
|
|
&remaining,
|
|
ApplyOffset(&configurable, iter.opt_offset));
|
|
if (remaining.empty()) { // Are there more options left?
|
|
break;
|
|
} else if (!s.ok()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (unused != nullptr && !remaining.empty()) {
|
|
unused->insert(remaining.begin(), remaining.end());
|
|
}
|
|
if (config_options.ignore_unknown_options) {
|
|
s = Status::OK();
|
|
} else if (s.ok() && unused == nullptr && !remaining.empty()) {
|
|
s = Status::NotFound("Could not find option: ", remaining.begin()->first);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Updates the object with the named-value property values, returning OK on
|
|
* succcess. Any properties that were found are removed from the options list;
|
|
* upon return only options that were not found in this opt_map remain.
|
|
|
|
* Returns:
|
|
* - OK if ignore_unknown_options is set
|
|
* - InvalidArgument, if any option was invalid
|
|
* - NotSupported, if any option is unsupported and ignore_unsupported_options
|
|
is OFF
|
|
* - OK, if no option was invalid or not supported (or ignored)
|
|
*/
|
|
Status ConfigurableHelper::ConfigureSomeOptions(
|
|
const ConfigOptions& config_options, Configurable& configurable,
|
|
const std::unordered_map<std::string, OptionTypeInfo>& type_map,
|
|
std::unordered_map<std::string, std::string>* options, void* opt_ptr) {
|
|
Status result = Status::OK(); // The last non-OK result (if any)
|
|
Status notsup = Status::OK(); // The last NotSupported result (if any)
|
|
std::string elem_name;
|
|
int found = 1;
|
|
std::unordered_set<std::string> unsupported;
|
|
// While there are unused properties and we processed at least one,
|
|
// go through the remaining unused properties and attempt to configure them.
|
|
while (found > 0 && !options->empty()) {
|
|
found = 0;
|
|
notsup = Status::OK();
|
|
for (auto it = options->begin(); it != options->end();) {
|
|
const std::string& opt_name = configurable.GetOptionName(it->first);
|
|
const std::string& opt_value = it->second;
|
|
const auto opt_info =
|
|
OptionTypeInfo::Find(opt_name, type_map, &elem_name);
|
|
if (opt_info == nullptr) { // Did not find the option. Skip it
|
|
++it;
|
|
} else {
|
|
Status s = ConfigureOption(config_options, configurable, *opt_info,
|
|
opt_name, elem_name, opt_value, opt_ptr);
|
|
if (s.IsNotFound()) {
|
|
++it;
|
|
} else if (s.IsNotSupported()) {
|
|
notsup = s;
|
|
unsupported.insert(it->first);
|
|
++it; // Skip it for now
|
|
} else {
|
|
found++;
|
|
it = options->erase(it);
|
|
if (!s.ok()) {
|
|
result = s;
|
|
}
|
|
}
|
|
}
|
|
} // End for all remaining options
|
|
} // End while found one or options remain
|
|
|
|
// Now that we have been through the list, remove any unsupported
|
|
for (const auto& u : unsupported) {
|
|
auto it = options->find(u);
|
|
if (it != options->end()) {
|
|
options->erase(it);
|
|
}
|
|
}
|
|
if (config_options.ignore_unknown_options) {
|
|
if (!result.ok()) {
|
|
result.PermitUncheckedError();
|
|
}
|
|
if (!notsup.ok()) {
|
|
notsup.PermitUncheckedError();
|
|
}
|
|
return Status::OK();
|
|
} else if (!result.ok()) {
|
|
if (!notsup.ok()) {
|
|
notsup.PermitUncheckedError();
|
|
}
|
|
return result;
|
|
} else if (config_options.ignore_unsupported_options) {
|
|
if (!notsup.ok()) {
|
|
notsup.PermitUncheckedError();
|
|
}
|
|
return Status::OK();
|
|
} else {
|
|
return notsup;
|
|
}
|
|
}
|
|
|
|
Status ConfigurableHelper::ConfigureSingleOption(
|
|
const ConfigOptions& config_options, Configurable& configurable,
|
|
const std::string& name, const std::string& value) {
|
|
const std::string& opt_name = configurable.GetOptionName(name);
|
|
std::string elem_name;
|
|
void* opt_ptr = nullptr;
|
|
const auto opt_info =
|
|
FindOption(configurable, opt_name, &elem_name, &opt_ptr);
|
|
if (opt_info == nullptr) {
|
|
return Status::NotFound("Could not find option: ", name);
|
|
} else {
|
|
return ConfigureOption(config_options, configurable, *opt_info, opt_name,
|
|
elem_name, value, opt_ptr);
|
|
}
|
|
}
|
|
Status ConfigurableHelper::ConfigureCustomizableOption(
|
|
const ConfigOptions& config_options, Configurable& configurable,
|
|
const OptionTypeInfo& opt_info, const std::string& opt_name,
|
|
const std::string& name, const std::string& value, void* opt_ptr) {
|
|
Customizable* custom = opt_info.AsRawPointer<Customizable>(opt_ptr);
|
|
ConfigOptions copy = config_options;
|
|
if (opt_info.IsMutable()) {
|
|
// This option is mutable. Pass that property on to any subsequent calls
|
|
copy.mutable_options_only = false;
|
|
}
|
|
|
|
if (opt_info.IsMutable() || !config_options.mutable_options_only) {
|
|
// Either the option is mutable, or we are processing all of the options
|
|
if (opt_name == name || name == OptionTypeInfo::kIdPropName() ||
|
|
EndsWith(opt_name, OptionTypeInfo::kIdPropSuffix())) {
|
|
return configurable.ParseOption(copy, opt_info, name, value, opt_ptr);
|
|
} else if (value.empty()) {
|
|
return Status::OK();
|
|
} else if (custom == nullptr || !StartsWith(name, custom->GetId() + ".")) {
|
|
return configurable.ParseOption(copy, opt_info, name, value, opt_ptr);
|
|
} else if (value.find('=') != std::string::npos) {
|
|
return custom->ConfigureFromString(copy, value);
|
|
} else {
|
|
return custom->ConfigureOption(copy, name, value);
|
|
}
|
|
} else {
|
|
// We are processing immutable options, which means that we cannot change
|
|
// the Customizable object itself, but could change its mutable properties.
|
|
// Check to make sure that nothing is trying to change the Customizable
|
|
if (custom == nullptr) {
|
|
// We do not have a Customizable to configure. This is OK if the
|
|
// value is empty (nothing being configured) but an error otherwise
|
|
if (value.empty()) {
|
|
return Status::OK();
|
|
} else {
|
|
return Status::InvalidArgument("Option not changeable: " + opt_name);
|
|
}
|
|
} else if (EndsWith(opt_name, OptionTypeInfo::kIdPropSuffix()) ||
|
|
name == OptionTypeInfo::kIdPropName()) {
|
|
// We have a property of the form "id=value" or "table.id=value"
|
|
// This is OK if we ID/value matches the current customizable object
|
|
if (custom->GetId() == value) {
|
|
return Status::OK();
|
|
} else {
|
|
return Status::InvalidArgument("Option not changeable: " + opt_name);
|
|
}
|
|
} else if (opt_name == name) {
|
|
// The properties are of one of forms:
|
|
// name = { id = id; prop1 = value1; ... }
|
|
// name = { prop1=value1; prop2=value2; ... }
|
|
// name = ID
|
|
// Convert the value to a map and extract the ID
|
|
// If the ID does not match that of the current customizable, return an
|
|
// error. Otherwise, update the current customizable via the properties
|
|
// map
|
|
std::unordered_map<std::string, std::string> props;
|
|
std::string id;
|
|
Status s =
|
|
Configurable::GetOptionsMap(value, custom->GetId(), &id, &props);
|
|
if (!s.ok()) {
|
|
return s;
|
|
} else if (custom->GetId() != id) {
|
|
return Status::InvalidArgument("Option not changeable: " + opt_name);
|
|
} else if (props.empty()) {
|
|
return Status::OK();
|
|
} else {
|
|
return custom->ConfigureFromMap(copy, props);
|
|
}
|
|
} else {
|
|
// Attempting to configure one of the properties of the customizable
|
|
// Let it through
|
|
return custom->ConfigureOption(copy, name, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
Status ConfigurableHelper::ConfigureOption(
|
|
const ConfigOptions& config_options, Configurable& configurable,
|
|
const OptionTypeInfo& opt_info, const std::string& opt_name,
|
|
const std::string& name, const std::string& value, void* opt_ptr) {
|
|
if (opt_info.IsCustomizable()) {
|
|
return ConfigureCustomizableOption(config_options, configurable, opt_info,
|
|
opt_name, name, value, opt_ptr);
|
|
} else if (opt_name == name) {
|
|
return configurable.ParseOption(config_options, opt_info, opt_name, value,
|
|
opt_ptr);
|
|
} else if (opt_info.IsStruct() || opt_info.IsConfigurable()) {
|
|
return configurable.ParseOption(config_options, opt_info, name, value,
|
|
opt_ptr);
|
|
} else {
|
|
return Status::NotFound("Could not find option: ", name);
|
|
}
|
|
}
|
|
|
|
//*******************************************************************************
|
|
//
|
|
// Methods for Converting Options into strings
|
|
//
|
|
//*******************************************************************************
|
|
|
|
Status Configurable::GetOptionString(const ConfigOptions& config_options,
|
|
std::string* result) const {
|
|
assert(result);
|
|
result->clear();
|
|
return ConfigurableHelper::SerializeOptions(config_options, *this, "",
|
|
result);
|
|
}
|
|
|
|
std::string Configurable::ToString(const ConfigOptions& config_options,
|
|
const std::string& prefix) const {
|
|
std::string result = SerializeOptions(config_options, prefix);
|
|
if (result.empty() || result.find('=') == std::string::npos) {
|
|
return result;
|
|
} else {
|
|
return "{" + result + "}";
|
|
}
|
|
}
|
|
|
|
std::string Configurable::SerializeOptions(const ConfigOptions& config_options,
|
|
const std::string& header) const {
|
|
std::string result;
|
|
Status s = ConfigurableHelper::SerializeOptions(config_options, *this, header,
|
|
&result);
|
|
assert(s.ok());
|
|
return result;
|
|
}
|
|
|
|
Status Configurable::GetOption(const ConfigOptions& config_options,
|
|
const std::string& name,
|
|
std::string* value) const {
|
|
return ConfigurableHelper::GetOption(config_options, *this,
|
|
GetOptionName(name), value);
|
|
}
|
|
|
|
Status ConfigurableHelper::GetOption(const ConfigOptions& config_options,
|
|
const Configurable& configurable,
|
|
const std::string& short_name,
|
|
std::string* value) {
|
|
// Look for option directly
|
|
assert(value);
|
|
value->clear();
|
|
|
|
std::string opt_name;
|
|
void* opt_ptr = nullptr;
|
|
const auto opt_info =
|
|
FindOption(configurable, short_name, &opt_name, &opt_ptr);
|
|
if (opt_info != nullptr) {
|
|
ConfigOptions embedded = config_options;
|
|
embedded.delimiter = ";";
|
|
if (short_name == opt_name) {
|
|
return opt_info->Serialize(embedded, opt_name, opt_ptr, value);
|
|
} else if (opt_info->IsStruct()) {
|
|
return opt_info->Serialize(embedded, opt_name, opt_ptr, value);
|
|
} else if (opt_info->IsConfigurable()) {
|
|
auto const* config = opt_info->AsRawPointer<Configurable>(opt_ptr);
|
|
if (config != nullptr) {
|
|
return config->GetOption(embedded, opt_name, value);
|
|
}
|
|
}
|
|
}
|
|
return Status::NotFound("Cannot find option: ", short_name);
|
|
}
|
|
|
|
Status ConfigurableHelper::SerializeOptions(const ConfigOptions& config_options,
|
|
const Configurable& configurable,
|
|
const std::string& prefix,
|
|
std::string* result) {
|
|
assert(result);
|
|
for (auto const& opt_iter : configurable.options_) {
|
|
if (opt_iter.type_map != nullptr) {
|
|
for (const auto& map_iter : *(opt_iter.type_map)) {
|
|
const auto& opt_name = map_iter.first;
|
|
const auto& opt_info = map_iter.second;
|
|
if (opt_info.ShouldSerialize()) {
|
|
std::string value;
|
|
Status s;
|
|
void* opt_ptr = ApplyOffset(&configurable, opt_iter.opt_offset);
|
|
if (!config_options.mutable_options_only) {
|
|
s = opt_info.Serialize(config_options, prefix + opt_name, opt_ptr,
|
|
&value);
|
|
} else if (opt_info.IsMutable()) {
|
|
ConfigOptions copy = config_options;
|
|
copy.mutable_options_only = false;
|
|
s = opt_info.Serialize(copy, prefix + opt_name, opt_ptr, &value);
|
|
} else if (opt_info.IsConfigurable()) {
|
|
// If it is a Configurable and we are either printing all of the
|
|
// details or not printing only the name, this option should be
|
|
// included in the list
|
|
if (config_options.IsDetailed() ||
|
|
!opt_info.IsEnabled(OptionTypeFlags::kStringNameOnly)) {
|
|
s = opt_info.Serialize(config_options, prefix + opt_name, opt_ptr,
|
|
&value);
|
|
}
|
|
}
|
|
if (!s.ok()) {
|
|
return s;
|
|
} else if (!value.empty()) {
|
|
// <prefix><opt_name>=<value><delimiter>
|
|
result->append(prefix + opt_name + "=" + value +
|
|
config_options.delimiter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
//********************************************************************************
|
|
//
|
|
// Methods for listing the options from Configurables
|
|
//
|
|
//********************************************************************************
|
|
Status Configurable::GetOptionNames(
|
|
const ConfigOptions& config_options,
|
|
std::unordered_set<std::string>* result) const {
|
|
assert(result);
|
|
return ConfigurableHelper::ListOptions(config_options, *this, "", result);
|
|
}
|
|
|
|
Status ConfigurableHelper::ListOptions(
|
|
const ConfigOptions& config_options, const Configurable& configurable,
|
|
const std::string& prefix, std::unordered_set<std::string>* result) {
|
|
Status status;
|
|
for (auto const& opt_iter : configurable.options_) {
|
|
if (opt_iter.type_map != nullptr) {
|
|
for (const auto& map_iter : *(opt_iter.type_map)) {
|
|
const auto& opt_name = map_iter.first;
|
|
const auto& opt_info = map_iter.second;
|
|
// If the option is no longer used in rocksdb and marked as deprecated,
|
|
// we skip it in the serialization.
|
|
if (!opt_info.IsDeprecated() && !opt_info.IsAlias()) {
|
|
if (!config_options.mutable_options_only) {
|
|
result->emplace(prefix + opt_name);
|
|
} else if (opt_info.IsMutable()) {
|
|
result->emplace(prefix + opt_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
//*******************************************************************************
|
|
//
|
|
// Methods for Comparing Configurables
|
|
//
|
|
//*******************************************************************************
|
|
|
|
bool Configurable::AreEquivalent(const ConfigOptions& config_options,
|
|
const Configurable* other,
|
|
std::string* name) const {
|
|
assert(name);
|
|
name->clear();
|
|
if (this == other || config_options.IsCheckDisabled()) {
|
|
return true;
|
|
} else if (other != nullptr) {
|
|
return ConfigurableHelper::AreEquivalent(config_options, *this, *other,
|
|
name);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Configurable::OptionsAreEqual(const ConfigOptions& config_options,
|
|
const OptionTypeInfo& opt_info,
|
|
const std::string& opt_name,
|
|
const void* const this_ptr,
|
|
const void* const that_ptr,
|
|
std::string* mismatch) const {
|
|
if (opt_info.AreEqual(config_options, opt_name, this_ptr, that_ptr,
|
|
mismatch)) {
|
|
return true;
|
|
} else if (opt_info.AreEqualByName(config_options, opt_name, this_ptr,
|
|
that_ptr)) {
|
|
*mismatch = "";
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ConfigurableHelper::AreEquivalent(const ConfigOptions& config_options,
|
|
const Configurable& this_one,
|
|
const Configurable& that_one,
|
|
std::string* mismatch) {
|
|
assert(mismatch != nullptr);
|
|
for (auto const& o : this_one.options_) {
|
|
const auto this_offset = this_one.GetOptionsPtr(o.name);
|
|
const auto that_offset = that_one.GetOptionsPtr(o.name);
|
|
if (this_offset != that_offset) {
|
|
if (this_offset == nullptr || that_offset == nullptr) {
|
|
return false;
|
|
} else if (o.type_map != nullptr) {
|
|
for (const auto& map_iter : *(o.type_map)) {
|
|
const auto& opt_info = map_iter.second;
|
|
if (config_options.IsCheckEnabled(opt_info.GetSanityLevel())) {
|
|
if (!config_options.mutable_options_only) {
|
|
if (!this_one.OptionsAreEqual(config_options, opt_info,
|
|
map_iter.first, this_offset,
|
|
that_offset, mismatch)) {
|
|
return false;
|
|
}
|
|
} else if (opt_info.IsMutable()) {
|
|
ConfigOptions copy = config_options;
|
|
copy.mutable_options_only = false;
|
|
if (!this_one.OptionsAreEqual(copy, opt_info, map_iter.first,
|
|
this_offset, that_offset,
|
|
mismatch)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Status Configurable::GetOptionsMap(
|
|
const std::string& value, const std::string& default_id, std::string* id,
|
|
std::unordered_map<std::string, std::string>* props) {
|
|
assert(id);
|
|
assert(props);
|
|
Status status;
|
|
if (value.empty() || value == kNullptrString) {
|
|
*id = default_id;
|
|
} else if (value.find('=') == std::string::npos) {
|
|
*id = value;
|
|
} else {
|
|
status = StringToMap(value, props);
|
|
if (!status.ok()) { // There was an error creating the map.
|
|
*id = value; // Treat the value as id
|
|
props->clear(); // Clear the properties
|
|
status = Status::OK(); // and ignore the error
|
|
} else {
|
|
auto iter = props->find(OptionTypeInfo::kIdPropName());
|
|
if (iter != props->end()) {
|
|
*id = iter->second;
|
|
props->erase(iter);
|
|
if (*id == kNullptrString) {
|
|
id->clear();
|
|
}
|
|
} else if (!default_id.empty()) {
|
|
*id = default_id;
|
|
} else { // No id property and no default
|
|
*id = value; // Treat the value as id
|
|
props->clear(); // Clear the properties
|
|
}
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
} // namespace ROCKSDB_NAMESPACE
|