Commit dd2f2ef7 authored by Anton Likhtarov's avatar Anton Likhtarov Committed by Facebook Github Bot

folly::settings: simplify; define/declare split; Metadata

Summary:
- Let's enforce a single definition. Also makes the implementation much simpler.
- Collect all static info about a setting in a Metadata struct

Differential Revision: D7544210

fbshipit-source-id: d3f2ef617bd817d355a37e11fa9425b6a4259384
parent f09d03aa
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Settings.h"
#include <folly/experimental/settings/Settings.h>
#include <map>
......@@ -23,74 +23,29 @@ namespace folly {
namespace settings {
namespace detail {
namespace {
class Signature {
public:
Signature(
const std::type_info& type,
std::string defaultString,
std::string desc,
bool unique)
: type_(type),
defaultString_(std::move(defaultString)),
desc_(std::move(desc)),
unique_(unique) {}
bool operator==(const Signature& other) const {
/* unique_ field is ignored on purpose */
return type_ == other.type_ && defaultString_ == other.defaultString_ &&
desc_ == other.desc_;
}
bool unique() const {
return unique_;
}
private:
std::type_index type_;
std::string defaultString_;
std::string desc_;
bool unique_;
};
using SettingsMap = std::
map<std::string, std::pair<Signature, std::shared_ptr<SettingCoreBase>>>;
using SettingsMap = std::map<std::string, SettingCoreBase*>;
Synchronized<SettingsMap>& settingsMap() {
static Indestructible<Synchronized<SettingsMap>> map;
return *map;
}
} // namespace
std::shared_ptr<SettingCoreBase> registerImpl(
StringPiece project,
StringPiece name,
const std::type_info& type,
StringPiece defaultString,
StringPiece desc,
bool unique,
std::shared_ptr<SettingCoreBase> base) {
if (project.empty() || project.find('_') != std::string::npos) {
void registerSetting(SettingCoreBase& core) {
if (core.meta().project.empty() ||
core.meta().project.find('_') != std::string::npos) {
throw std::logic_error(
"Setting project must be nonempty and cannot contain underscores: " +
project.str());
core.meta().project.str());
}
auto fullname = project.str() + "_" + name.str();
Signature sig(type, defaultString.str(), desc.str(), unique);
auto fullname = core.meta().project.str() + "_" + core.meta().name.str();
auto mapPtr = settingsMap().wlock();
auto it = mapPtr->find(fullname);
if (it != mapPtr->end()) {
if (it->second.first == sig) {
if (unique || it->second.first.unique()) {
throw std::logic_error("FOLLY_SETTING not unique: " + fullname);
}
/* Identical SHARED setting in a different translation unit,
reuse it */
return it->second.second;
}
throw std::logic_error("Setting collision detected: " + fullname);
throw std::logic_error("FOLLY_SETTING already exists: " + fullname);
}
mapPtr->emplace(std::move(fullname), std::make_pair(std::move(sig), base));
return base;
mapPtr->emplace(std::move(fullname), &core);
}
} // namespace detail
......@@ -104,7 +59,7 @@ bool setFromString(
if (it == mapPtr->end()) {
return false;
}
it->second.second->setFromString(newValue, reason);
it->second->setFromString(newValue, reason);
return true;
}
......@@ -115,7 +70,7 @@ Optional<std::pair<std::string, std::string>> getAsString(
if (it == mapPtr->end()) {
return folly::none;
}
return it->second.second->getAsString();
return it->second->getAsString();
}
bool resetToDefault(StringPiece settingName) {
......@@ -124,21 +79,20 @@ bool resetToDefault(StringPiece settingName) {
if (it == mapPtr->end()) {
return false;
}
it->second.second->resetToDefault();
it->second->resetToDefault();
return true;
}
void forEachSetting(
const std::function<
void(StringPiece, StringPiece, StringPiece, const std::type_info&)>&
const std::function<void(const SettingMetadata&, StringPiece, StringPiece)>&
func) {
detail::SettingsMap map;
/* Note that this won't hold the lock over the callback, which is
what we want since the user might call other settings:: APIs */
map = *detail::settingsMap().rlock();
for (const auto& kv : map) {
auto value = kv.second.second->getAsString();
func(kv.first, value.first, value.second, kv.second.second->typeId());
auto value = kv.second->getAsString();
func(kv.second->meta(), value.first, value.second);
}
}
......
......@@ -18,128 +18,141 @@
#include <functional>
#include <string>
#include <folly/Indestructible.h>
#include <folly/Range.h>
#include <folly/experimental/settings/detail/SettingsImpl.h>
namespace folly {
namespace settings {
namespace detail {
template <class SettingMeta>
class SettingHandle {
public:
/**
* Static information about the setting definition
*/
struct SettingMetadata {
/**
* Project string.
*/
folly::StringPiece project;
/**
* Setting name within the project.
*/
folly::StringPiece name;
/**
* Setting type
* String representation of the type.
*/
using Type = typename SettingMeta::Type;
folly::StringPiece typeStr;
/**
* Returns the setting's current value.
* Note that the returned reference is not guaranteed to be long-lived
* and should not be saved anywhere.
* typeid() of the type.
*/
const std::type_info& typeId;
/**
* String representation of the default value.
* (note: string literal default values will be stringified with quotes)
*/
folly::StringPiece defaultStr;
/**
* Setting description field.
*/
folly::StringPiece description;
};
namespace detail {
template <class Type>
class Setting {
public:
/**
* Returns the setting's current value. Note that the returned
* reference is not guaranteed to be long-lived and should not be
* saved anywhere. In particular, a set() call might invalidate a
* reference obtained here after some amount of time (on the order
* of minutes).
*/
const Type& operator*() const {
return core().get();
return core_.get();
}
const Type* operator->() const {
return &core().get();
return &core_.get();
}
/**
* Atomically updates the setting's current value.
* Atomically updates the setting's current value. Will invalidate
* any previous calls to operator*() after some amount of time (on
* the order of minutes).
*
* @param reason Will be stored with the current value, useful for debugging.
*/
static void set(const Type& t, folly::StringPiece reason = "api") {
core().set(t, reason);
void set(const Type& t, StringPiece reason = "api") {
core_.set(t, reason);
}
SettingHandle() {
/* Ensure setting is registered */
core();
}
Setting(SettingMetadata meta, Type defaultValue)
: meta_(std::move(meta)), core_(meta_, std::move(defaultValue)) {}
private:
static SettingCore<SettingMeta>& core() {
static /* library-local */
Indestructible<std::shared_ptr<SettingCoreBase>>
core = registerImpl(
SettingMeta::project(),
SettingMeta::name(),
typeid(typename SettingMeta::Type),
SettingMeta::defaultString(),
SettingMeta::desc(),
SettingMeta::unique(),
std::make_shared<SettingCore<SettingMeta>>());
return static_cast<SettingCore<SettingMeta>&>(**core);
}
SettingMetadata meta_;
SettingCore<Type> core_;
};
} // namespace detail
#define FOLLY_SETTING_IMPL(_project, _Type, _name, _def, _desc, _unique) \
namespace { \
struct FOLLY_SETTINGS_META__##_project##_##_name { \
using Type = _Type; \
static folly::StringPiece project() { \
return #_project; \
} \
static folly::StringPiece name() { \
return #_name; \
} \
static Type def() { \
return _def; \
} \
static folly::StringPiece defaultString() { \
return #_def; \
} \
static folly::StringPiece desc() { \
return _desc; \
} \
static bool unique() { \
return _unique; \
} \
}; \
folly::settings::detail::SettingHandle< \
FOLLY_SETTINGS_META__##_project##_##_name> \
SETTING_##_project##_##_name; \
} \
/* hack to require a trailing semicolon */ \
int FOLLY_SETTINGS_IGNORE__##_project##_##_name()
/**
* Defines a setting.
*
* FOLLY_SETTING_SHARED(): syntactically, think of it like a class
* definition. You can place the identical setting definitions in
* distinct translation units, but not in the same translation unit.
* In particular, you can place a setting definition in a header file
* and include it from multiple .cpp files - all of these definitions
* will refer to a single setting.
*
* FOLLY_SETTING() variant can only be placed in a single translation unit
* FOLLY_SETTING_DEFINE() can only be placed in a single translation unit
* and will be checked against accidental collisions.
*
* The setting API can be accessed via SETTING_project_name::<api_func>() and
* is documented in the SettingHandle class.
* The setting API can be accessed via FOLLY_SETTING(project, name).<api_func>()
* and is documented in the Setting class.
*
* While the specific SETTING_project_name classes are declared
* inplace and are namespace local, all settings for a given project
* share a common namespace and collisions are verified at runtime on
* All settings for a common namespace; (project, name) must be unique
* for the whole program. Collisions are verified at runtime on
* program startup.
*
* @param _project Project identifier, can only contain [a-zA-Z0-9]
* @param _Type setting value type
* @param _name setting name within the project, can only contain [_a-zA-Z0-9].
* The string "<project>_<name>" must be unique for the whole program.
* @param _Type setting value type
* @param _def default value for the setting
* @param _desc setting documentation
*/
#define FOLLY_SETTING(_project, _Type, _name, _def, _desc) \
FOLLY_SETTING_IMPL(_project, _Type, _name, _def, _desc, /* unique */ true)
#define FOLLY_SETTING_SHARED(_project, _Type, _name, _def, _desc) \
FOLLY_SETTING_IMPL(_project, _Type, _name, _def, _desc, /* unique */ false)
#define FOLLY_SETTING_DEFINE(_project, _name, _Type, _def, _desc) \
/* Meyers singleton to avoid SIOF */ \
inline folly::settings::detail::Setting<_Type>& \
FOLLY_SETTINGS_FUNC__##_project##_##_name() { \
static folly::Indestructible<folly::settings::detail::Setting<_Type>> \
setting( \
folly::settings::SettingMetadata{ \
#_project, #_name, #_Type, typeid(_Type), #_def, _desc}, \
_def); \
return *setting; \
} \
/* Ensure the setting is registered even if not used in program */ \
auto& FOLLY_SETTINGS_INIT__##_project##_##_name = \
FOLLY_SETTINGS_FUNC__##_project##_##_name()
/**
* Declares a setting that's defined elsewhere.
*/
#define FOLLY_SETTING_DECLARE(_project, _name, _Type) \
folly::settings::detail::Setting<_Type>& \
FOLLY_SETTINGS_FUNC__##_project##_##_name()
/**
* Accesses a defined setting.
* Rationale for the macro:
* 1) Searchability, all settings access is done via FOLLY_SETTING(...)
* 2) Prevents omitting trailing () by accident, which could
* lead to bugs like `auto value = *FOLLY_SETTING_project_name;`,
* which compiles but dereferences the function pointer instead of
* the setting itself.
*/
#define FOLLY_SETTING(_project, _name) \
FOLLY_SETTINGS_FUNC__##_project##_##_name()
/**
* Look up a setting by name, and update the value from a string representation.
......@@ -170,13 +183,12 @@ bool resetToDefault(folly::StringPiece settingName);
/**
* Iterates over all known settings and calls
* func(name, to<string>(value), reason, typeid(Type)) for each.
* func(meta, to<string>(value), reason) for each.
*/
void forEachSetting(const std::function<void(
folly::StringPiece,
folly::StringPiece,
folly::StringPiece,
const std::type_info&)>& func);
void forEachSetting(
const std::function<
void(const SettingMetadata&, folly::StringPiece, folly::StringPiece)>&
func);
} // namespace settings
} // namespace folly
......@@ -24,9 +24,13 @@
#include <folly/Conv.h>
#include <folly/Range.h>
#include <folly/SharedMutex.h>
#include <folly/ThreadLocal.h>
namespace folly {
namespace settings {
struct SettingMetadata;
namespace detail {
template <class Type>
......@@ -44,23 +48,15 @@ class SettingCoreBase {
virtual void setFromString(StringPiece newValue, StringPiece reason) = 0;
virtual std::pair<std::string, std::string> getAsString() const = 0;
virtual void resetToDefault() = 0;
virtual const std::type_info& typeId() const = 0;
virtual const SettingMetadata& meta() const = 0;
virtual ~SettingCoreBase() {}
};
std::shared_ptr<SettingCoreBase> registerImpl(
StringPiece project,
StringPiece name,
const std::type_info& type,
StringPiece defaultString,
StringPiece desc,
bool unique,
std::shared_ptr<SettingCoreBase> base);
void registerSetting(SettingCoreBase& core);
template <class SettingMeta>
template <class Type>
class SettingCore : public SettingCoreBase {
public:
using Type = typename SettingMeta::Type;
using Contents = SettingContents<Type>;
void setFromString(StringPiece newValue, StringPiece reason) override {
......@@ -72,10 +68,10 @@ class SettingCore : public SettingCoreBase {
to<std::string>(contents.value), contents.updateReason);
}
void resetToDefault() override {
set(SettingMeta::def(), "default");
set(defaultValue_, "default");
}
const std::type_info& typeId() const override {
return typeid(Type);
const SettingMetadata& meta() const override {
return meta_;
}
const Type& get() const {
......@@ -87,24 +83,34 @@ class SettingCore : public SettingCoreBase {
++(*globalVersion_);
}
SettingCore()
: globalValue_(
std::make_shared<Contents>("default", SettingMeta::def())) {}
~SettingCore() {}
SettingCore(const SettingMetadata& meta, Type defaultValue)
: meta_(meta),
defaultValue_(std::move(defaultValue)),
globalValue_(std::make_shared<Contents>("default", defaultValue_)),
localValue_([]() {
return new CachelinePadded<
Indestructible<std::pair<size_t, std::shared_ptr<Contents>>>>(
0, nullptr);
}) {
registerSetting(*this);
}
private:
const SettingMetadata& meta_;
const Type defaultValue_;
SharedMutex globalLock_;
std::shared_ptr<Contents> globalValue_;
/* Local versions start at 0, this will force a read on first local access. */
CachelinePadded<std::atomic<size_t>> globalVersion_{1};
ThreadLocal<CachelinePadded<
Indestructible<std::pair<size_t, std::shared_ptr<Contents>>>>>
localValue_;
std::shared_ptr<Contents>& tlValue() {
thread_local CachelinePadded<
Indestructible<std::pair<size_t, std::shared_ptr<Contents>>>>
localValue;
auto& value = **localValue;
auto& value = ***localValue_;
while (value.first < *globalVersion_) {
/* If this destroys the old value, do it without holding the lock */
value.second.reset();
......
......@@ -26,11 +26,11 @@ settings_get_bench 1.73ns 577.36M
============================================================================
*/
FOLLY_SETTING(follytest, int, benchmarked, 100, "desc");
FOLLY_SETTING_DEFINE(follytest, benchmarked, int, 100, "desc");
BENCHMARK(settings_get_bench, iters) {
for (unsigned int i = 0; i < iters; ++i) {
auto value = *SETTING_follytest_benchmarked;
auto value = *FOLLY_SETTING(follytest, benchmarked);
folly::doNotOptimizeAway(value);
}
}
......
......@@ -21,55 +21,37 @@
#include "b.h"
namespace some_ns {
FOLLY_SETTING(follytest, std::string, some_flag, "default", "Description");
FOLLY_SETTING(
FOLLY_SETTING_DEFINE(
follytest,
some_flag,
std::string,
"default",
"Description");
FOLLY_SETTING_DEFINE(
follytest,
unused,
std::string,
"unused_default",
"Not used, but should still be in the list");
// Enable to test runtime collision checking logic
#if 0
FOLLY_SETTING(follytest, std::string, internal_flag_to_a, "collision_with_a",
"Collision_with_a");
FOLLY_SETTING_DEFINE(follytest, internal_flag_to_a, std::string,
"collision_with_a",
"Collision_with_a");
#endif
} // namespace some_ns
TEST(Settings, basic) {
std::string allFlags;
folly::settings::forEachSetting([&allFlags](
folly::StringPiece name,
folly::StringPiece value,
folly::StringPiece reason,
const std::type_info& type) {
std::string typeName;
if (type == typeid(int)) {
typeName = "int";
} else if (type == typeid(std::string)) {
typeName = "std::string";
} else {
ASSERT_FALSE(true);
}
allFlags += folly::sformat("{} {} {} {}\n", name, value, reason, typeName);
});
EXPECT_EQ(
allFlags,
"follytest_internal_flag_to_a 789 default int\n"
"follytest_internal_flag_to_b test default std::string\n"
"follytest_public_flag_to_a 456 default int\n"
"follytest_public_flag_to_b basdf default std::string\n"
"follytest_some_flag default default std::string\n"
"follytest_unused unused_default default std::string\n");
EXPECT_EQ(a_ns::a_func(), 1245);
EXPECT_EQ(b_ns::b_func(), "testbasdf");
EXPECT_EQ(*some_ns::SETTING_follytest_some_flag, "default");
EXPECT_EQ(
std::string(some_ns::SETTING_follytest_some_flag->c_str()), "default");
a_ns::SETTING_follytest_public_flag_to_a.set(100);
EXPECT_EQ(*a_ns::SETTING_follytest_public_flag_to_a, 100);
EXPECT_EQ(*some_ns::FOLLY_SETTING(follytest, some_flag), "default");
// Test -> API
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, some_flag)->size(), 7);
a_ns::FOLLY_SETTING(follytest, public_flag_to_a).set(100);
EXPECT_EQ(*a_ns::FOLLY_SETTING(follytest, public_flag_to_a), 100);
EXPECT_EQ(a_ns::getRemote(), 100);
a_ns::setRemote(200);
EXPECT_EQ(*a_ns::SETTING_follytest_public_flag_to_a, 200);
EXPECT_EQ(*a_ns::FOLLY_SETTING(follytest, public_flag_to_a), 200);
EXPECT_EQ(a_ns::getRemote(), 200);
auto res = folly::settings::getAsString("follytest_public_flag_to_a");
......@@ -82,7 +64,7 @@ TEST(Settings, basic) {
EXPECT_TRUE(folly::settings::setFromString(
"follytest_public_flag_to_a", "300", "from_string"));
EXPECT_EQ(*a_ns::SETTING_follytest_public_flag_to_a, 300);
EXPECT_EQ(*a_ns::FOLLY_SETTING(follytest, public_flag_to_a), 300);
EXPECT_EQ(a_ns::getRemote(), 300);
res = folly::settings::getAsString("follytest_public_flag_to_a");
EXPECT_TRUE(res.hasValue());
......@@ -92,8 +74,40 @@ TEST(Settings, basic) {
EXPECT_FALSE(folly::settings::setFromString(
"follytest_nonexisting", "300", "from_string"));
std::string allFlags;
folly::settings::forEachSetting(
[&allFlags](
const folly::settings::SettingMetadata& meta,
folly::StringPiece value,
folly::StringPiece reason) {
if (meta.typeId == typeid(int)) {
EXPECT_EQ(meta.typeStr, "int");
} else if (meta.typeId == typeid(std::string)) {
EXPECT_EQ(meta.typeStr, "std::string");
} else {
ASSERT_FALSE(true);
}
allFlags += folly::sformat(
"{}/{}/{}/{}/{}/{}/{}\n",
meta.project,
meta.name,
meta.typeStr,
meta.defaultStr,
meta.description,
value,
reason);
});
EXPECT_EQ(
allFlags,
"follytest/internal_flag_to_a/int/789/Desc of int/789/default\n"
"follytest/internal_flag_to_b/std::string/\"test\"/Desc of str/test/default\n"
"follytest/public_flag_to_a/int/456/Public flag to a/300/from_string\n"
"follytest/public_flag_to_b/std::string/\"basdf\"/Public flag to b/basdf/default\n"
"follytest/some_flag/std::string/\"default\"/Description/default/default\n"
"follytest/unused/std::string/\"unused_default\"/Not used, but should still be in the list/unused_default/default\n");
EXPECT_TRUE(folly::settings::resetToDefault("follytest_public_flag_to_a"));
EXPECT_EQ(*a_ns::SETTING_follytest_public_flag_to_a, 456);
EXPECT_EQ(*a_ns::FOLLY_SETTING(follytest, public_flag_to_a), 456);
EXPECT_EQ(a_ns::getRemote(), 456);
EXPECT_FALSE(folly::settings::resetToDefault("follytest_nonexisting"));
......
......@@ -18,21 +18,22 @@
#include <folly/experimental/settings/Settings.h>
namespace a_ns {
FOLLY_SETTING_DEFINE(follytest, public_flag_to_a, int, 456, "Public flag to a");
namespace {
FOLLY_SETTING(follytest, int, internal_flag_to_a, 789, "Desc of int");
FOLLY_SETTING_DEFINE(follytest, internal_flag_to_a, int, 789, "Desc of int");
}
int a_func() {
return *SETTING_follytest_public_flag_to_a +
*SETTING_follytest_internal_flag_to_a;
return *FOLLY_SETTING(follytest, public_flag_to_a) +
*FOLLY_SETTING(follytest, internal_flag_to_a);
}
void setRemote(int value) {
SETTING_follytest_public_flag_to_a.set(value, "remote_set");
FOLLY_SETTING(follytest, public_flag_to_a).set(value, "remote_set");
}
int getRemote() {
return *SETTING_follytest_public_flag_to_a;
return *FOLLY_SETTING(follytest, public_flag_to_a);
}
} // namespace a_ns
......@@ -19,7 +19,7 @@
namespace a_ns {
FOLLY_SETTING_SHARED(follytest, int, public_flag_to_a, 456, "Public flag to a");
FOLLY_SETTING_DECLARE(follytest, public_flag_to_a, int);
int a_func();
void setRemote(int value);
......
......@@ -18,18 +18,26 @@
#include <folly/experimental/settings/Settings.h>
namespace b_ns {
namespace {
FOLLY_SETTING(
FOLLY_SETTING_DEFINE(
follytest,
public_flag_to_b,
std::string,
"basdf",
"Public flag to b");
namespace {
FOLLY_SETTING_DEFINE(
follytest,
internal_flag_to_b,
std::string,
"test",
"Desc of str");
}
std::string b_func() {
return *SETTING_follytest_internal_flag_to_b +
*SETTING_follytest_public_flag_to_b;
return *FOLLY_SETTING(follytest, internal_flag_to_b) +
*FOLLY_SETTING(follytest, public_flag_to_b);
}
} // namespace b_ns
......@@ -19,12 +19,7 @@
namespace b_ns {
FOLLY_SETTING_SHARED(
follytest,
std::string,
public_flag_to_b,
"basdf",
"Public flag to b");
FOLLY_SETTING_DECLARE(follytest, public_flag_to_b, std::string);
std::string b_func();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment