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

Move in global setting APIs into Snapshot

Summary: This forces users to take a Snapshot and makes global settings updates both atomic and more explicit.

Differential Revision: D10055254

fbshipit-source-id: 73ce34acdadad19d5b75e74ad26f13dc7763228d
parent 845a46cc
......@@ -50,56 +50,57 @@ void registerSetting(SettingCoreBase& core) {
} // namespace detail
bool setFromString(
StringPiece settingName,
StringPiece newValue,
StringPiece reason) {
Optional<SettingMetadata> getSettingsMeta(StringPiece settingName) {
auto mapPtr = detail::settingsMap().rlock();
auto it = mapPtr->find(settingName.str());
if (it == mapPtr->end()) {
return false;
return none;
}
it->second->setFromString(newValue, reason);
return true;
return it->second->meta();
}
Optional<SettingsInfo> getAsString(StringPiece settingName) {
bool Snapshot::setFromString(
StringPiece settingName,
StringPiece newValue,
StringPiece reason) {
auto mapPtr = detail::settingsMap().rlock();
auto it = mapPtr->find(settingName.str());
if (it == mapPtr->end()) {
return folly::none;
return false;
}
return it->second->getAsString();
it->second->setFromString(newValue, reason, this);
return true;
}
Optional<SettingMetadata> getSettingsMeta(StringPiece settingName) {
Optional<Snapshot::SettingsInfo> Snapshot::getAsString(
StringPiece settingName) const {
auto mapPtr = detail::settingsMap().rlock();
auto it = mapPtr->find(settingName.str());
if (it == mapPtr->end()) {
return folly::none;
return none;
}
return it->second->meta();
return it->second->getAsString(this);
}
bool resetToDefault(StringPiece settingName) {
bool Snapshot::resetToDefault(StringPiece settingName) {
auto mapPtr = detail::settingsMap().rlock();
auto it = mapPtr->find(settingName.str());
if (it == mapPtr->end()) {
return false;
}
it->second->resetToDefault();
it->second->resetToDefault(this);
return true;
}
void forEachSetting(
void Snapshot::forEachSetting(
const std::function<void(const SettingMetadata&, StringPiece, StringPiece)>&
func) {
func) const {
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->getAsString();
auto value = kv.second->getAsString(this);
func(kv.second->meta(), value.first, value.second);
}
}
......
......@@ -48,7 +48,7 @@ class SettingWrapper {
return core_.getWithHint(*TrivialPtr);
}
const T* operator->() const {
return &core_.getSlow();
return &core_.getSlow().value;
}
/**
......@@ -182,51 +182,11 @@ using TypeIdentityT = typename TypeIdentity<T>::type;
#define FOLLY_SETTING(_project, _name) \
FOLLY_SETTINGS_LOCAL_FUNC__##_project##_##_name(0)
/**
* Look up a setting by name, and update the value from a string representation.
*
* @returns True if the setting was successfully updated, false if no setting
* with that name was found.
* @throws std::runtime_error If there's a conversion error.
*/
bool setFromString(
folly::StringPiece settingName,
folly::StringPiece newValue,
folly::StringPiece reason);
/**
* Type that encapsulates the current pair of (to<string>(value), reason)
*/
using SettingsInfo = std::pair<std::string, std::string>;
/**
* @return If the setting exists, the current setting information.
* Empty Optional otherwise.
*/
folly::Optional<SettingsInfo> getAsString(folly::StringPiece settingName);
/**
* @return If the setting exists, returns the current settings metadata.
* Empty Optional otherwise.
*/
folly::Optional<SettingMetadata> getSettingsMeta(
folly::StringPiece settingName);
/**
* Reset the value of the setting identified by name to its default value.
* The reason will be set to "default".
*
* @return True if the setting was reset, false if the setting is not found.
*/
bool resetToDefault(folly::StringPiece settingName);
/**
* Iterates over all known settings and calls
* func(meta, to<string>(value), reason) for each.
*/
void forEachSetting(
const std::function<
void(const SettingMetadata&, folly::StringPiece, folly::StringPiece)>&
func);
Optional<SettingMetadata> getSettingsMeta(StringPiece settingName);
namespace detail {
......@@ -316,7 +276,42 @@ class Snapshot final : public detail::SnapshotBase {
* Apply all settings updates from this snapshot to the global state
* unconditionally.
*/
void publish();
void publish() override;
/**
* Look up a setting by name, and update the value from a string
* representation.
*
* @returns True if the setting was successfully updated, false if no setting
* with that name was found.
* @throws std::runtime_error If there's a conversion error.
*/
bool setFromString(
StringPiece settingName,
StringPiece newValue,
StringPiece reason) override;
/**
* @return If the setting exists, the current setting information.
* Empty Optional otherwise.
*/
Optional<SettingsInfo> getAsString(StringPiece settingName) const override;
/**
* Reset the value of the setting identified by name to its default value.
* The reason will be set to "default".
*
* @return True if the setting was reset, false if the setting is not found.
*/
bool resetToDefault(StringPiece settingName) override;
/**
* Iterates over all known settings and calls
* func(meta, to<string>(value), reason) for each.
*/
void forEachSetting(const std::function<
void(const SettingMetadata&, StringPiece, StringPiece)>&
func) const override;
private:
template <typename T>
......@@ -326,7 +321,7 @@ class Snapshot final : public detail::SnapshotBase {
namespace detail {
template <class T>
inline const T& SnapshotSettingWrapper<T>::operator*() const {
return snapshot_.get(core_);
return snapshot_.get(core_).value;
}
} // namespace detail
......
......@@ -50,14 +50,20 @@ struct SettingContents {
: updateReason(std::move(_reason)), value(std::forward<Args>(args)...) {}
};
class SnapshotBase;
class SettingCoreBase {
public:
using Key = intptr_t;
using Version = uint64_t;
virtual void setFromString(StringPiece newValue, StringPiece reason) = 0;
virtual std::pair<std::string, std::string> getAsString() const = 0;
virtual void resetToDefault() = 0;
virtual void setFromString(
StringPiece newValue,
StringPiece reason,
SnapshotBase* snapshot) = 0;
virtual std::pair<std::string, std::string> getAsString(
const SnapshotBase* snapshot) const = 0;
virtual void resetToDefault(SnapshotBase* snapshot) = 0;
virtual const SettingMetadata& meta() const = 0;
virtual ~SettingCoreBase() {}
......@@ -90,25 +96,27 @@ class BoxedValue {
* Stores a value that can be retrieved later
*/
template <class T>
explicit BoxedValue(const T& value) : value_(std::make_shared<T>(value)) {}
explicit BoxedValue(const SettingContents<T>& value)
: value_(std::make_shared<SettingContents<T>>(value)) {}
/**
* Stores a value that can be both retrieved later and optionally
* applied globally
*/
template <class T>
BoxedValue(const T& value, folly::StringPiece reason, SettingCore<T>& core)
: value_(std::make_shared<T>(value)),
publish_([value = value_, &core, r = reason.str()]() {
core.set(unboxImpl<T>(value.get()), r);
BoxedValue(const T& value, StringPiece reason, SettingCore<T>& core)
: value_(std::make_shared<SettingContents<T>>(reason.str(), value)),
publish_([value = value_, &core]() {
auto& contents = BoxedValue::unboxImpl<T>(value.get());
core.set(contents.value, contents.updateReason);
}) {}
/**
* Returns the reference to the stored value
*/
template <class T>
const T& unbox() const {
return unboxImpl<T>(value_.get());
const SettingContents<T>& unbox() const {
return BoxedValue::unboxImpl<T>(value_.get());
}
/**
......@@ -125,8 +133,8 @@ class BoxedValue {
std::function<void()> publish_;
template <class T>
static const T& unboxImpl(void* value) {
return *static_cast<const T*>(value);
static const SettingContents<T>& unboxImpl(void* value) {
return *static_cast<const SettingContents<T>*>(value);
}
};
......@@ -147,6 +155,56 @@ const BoxedValue* getSavedValue(
SettingCoreBase::Version at);
class SnapshotBase {
public:
/**
* Type that encapsulates the current pair of (to<string>(value), reason)
*/
using SettingsInfo = std::pair<std::string, std::string>;
/**
* Apply all settings updates from this snapshot to the global state
* unconditionally.
*/
virtual void publish() = 0;
/**
* Look up a setting by name, and update the value from a string
* representation.
*
* @returns True if the setting was successfully updated, false if no setting
* with that name was found.
* @throws std::runtime_error If there's a conversion error.
*/
virtual bool setFromString(
StringPiece settingName,
StringPiece newValue,
StringPiece reason) = 0;
/**
* @return If the setting exists, the current setting information.
* Empty Optional otherwise.
*/
virtual Optional<SettingsInfo> getAsString(StringPiece settingName) const = 0;
/**
* Reset the value of the setting identified by name to its default value.
* The reason will be set to "default".
*
* @return True if the setting was reset, false if the setting is not found.
*/
virtual bool resetToDefault(StringPiece settingName) = 0;
/**
* Iterates over all known settings and calls
* func(meta, to<string>(value), reason) for each.
*/
virtual void forEachSetting(
const std::function<
void(const SettingMetadata&, StringPiece, StringPiece)>& func)
const = 0;
virtual ~SnapshotBase();
protected:
detail::SettingCoreBase::Version at_;
std::unordered_map<detail::SettingCoreBase::Key, detail::BoxedValue>
......@@ -156,10 +214,9 @@ class SnapshotBase {
friend class SettingCore;
SnapshotBase();
virtual ~SnapshotBase();
template <class T>
const T& get(detail::SettingCore<T>& core) const {
const SettingContents<T>& get(const detail::SettingCore<T>& core) const {
auto it = snapshotValues_.find(core.getKey());
if (it != snapshotValues_.end()) {
return it->second.template unbox<T>();
......@@ -193,17 +250,24 @@ class SettingCore : public SettingCoreBase {
public:
using Contents = SettingContents<T>;
void setFromString(StringPiece newValue, StringPiece reason) override {
set(convertOrConstruct<T>(newValue), reason.str());
void setFromString(
StringPiece newValue,
StringPiece reason,
SnapshotBase* snapshot) override {
set(convertOrConstruct<T>(newValue), reason.str(), snapshot);
}
std::pair<std::string, std::string> getAsString() const override {
auto contents = *const_cast<SettingCore*>(this)->tlValue();
std::pair<std::string, std::string> getAsString(
const SnapshotBase* snapshot) const override {
auto& contents = snapshot ? snapshot->get(*this) : getSlow();
return std::make_pair(
to<std::string>(contents.value), contents.updateReason);
}
void resetToDefault() override {
set(defaultValue_, "default");
void resetToDefault(SnapshotBase* snapshot) override {
set(defaultValue_, "default", snapshot);
}
const SettingMetadata& meta() const override {
return meta_;
}
......@@ -218,8 +282,8 @@ class SettingCore : public SettingCoreBase {
std::atomic<uint64_t>& trivialStorage) const {
return getImpl(IsSmallPOD<T>(), trivialStorage);
}
const T& getSlow() const {
return getImpl(std::false_type{}, trivialStorage_);
const SettingContents<T>& getSlow() const {
return *tlValue();
}
/***
* SmallPOD version: just read the global atomic
......@@ -252,7 +316,7 @@ class SettingCore : public SettingCoreBase {
if (globalValue_) {
saveValueForOutstandingSnapshots(
getKey(), *settingVersion_, BoxedValue(globalValue_->value));
getKey(), *settingVersion_, BoxedValue(*globalValue_));
}
globalValue_ = std::make_shared<Contents>(reason.str(), t);
if (IsSmallPOD<T>::value) {
......@@ -296,14 +360,14 @@ class SettingCore : public SettingCoreBase {
Indestructible<std::pair<Version, std::shared_ptr<Contents>>>>>
localValue_;
FOLLY_ALWAYS_INLINE std::shared_ptr<Contents>& tlValue() {
FOLLY_ALWAYS_INLINE std::shared_ptr<Contents>& tlValue() const {
auto& value = ***localValue_;
if (LIKELY(value.first == *settingVersion_)) {
return value.second;
}
return tlValueSlow();
}
FOLLY_NOINLINE std::shared_ptr<Contents>& tlValueSlow() {
FOLLY_NOINLINE std::shared_ptr<Contents>& tlValueSlow() const {
auto& value = ***localValue_;
while (value.first < *settingVersion_) {
/* If this destroys the old value, do it without holding the lock */
......
......@@ -84,29 +84,43 @@ FOLLY_SETTING_DEFINE(
TEST(Settings, user_defined) {
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, user_defined)->value_, 100);
EXPECT_TRUE(
folly::settings::setFromString("follytest_user_defined", "a", "test"));
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, user_defined)->value_, 0);
{
auto info = folly::settings::getAsString("follytest_user_defined");
folly::settings::Snapshot sn;
EXPECT_TRUE(sn.setFromString("follytest_user_defined", "a", "test"));
sn.publish();
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, user_defined)->value_, 0);
}
{
folly::settings::Snapshot sn;
auto info = sn.getAsString("follytest_user_defined");
EXPECT_TRUE(info.hasValue());
EXPECT_EQ(info->first, "a_out");
EXPECT_EQ(info->second, "test");
}
EXPECT_THROW(
folly::settings::setFromString("follytest_user_defined", "c", "test2"),
std::runtime_error);
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, user_defined)->value_, 0);
{
auto info = folly::settings::getAsString("follytest_user_defined");
folly::settings::Snapshot sn;
EXPECT_THROW(
sn.setFromString("follytest_user_defined", "c", "test2"),
std::runtime_error);
sn.publish();
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, user_defined)->value_, 0);
}
{
folly::settings::Snapshot sn;
auto info = sn.getAsString("follytest_user_defined");
EXPECT_TRUE(info.hasValue());
EXPECT_EQ(info->first, "a_out");
EXPECT_EQ(info->second, "test");
}
EXPECT_TRUE(folly::settings::resetToDefault("follytest_user_defined"));
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, user_defined)->value_, 100);
{
auto info = folly::settings::getAsString("follytest_user_defined");
folly::settings::Snapshot sn;
EXPECT_TRUE(sn.resetToDefault("follytest_user_defined"));
sn.publish();
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, user_defined)->value_, 100);
}
{
folly::settings::Snapshot sn;
auto info = sn.getAsString("follytest_user_defined");
EXPECT_TRUE(info.hasValue());
EXPECT_EQ(info->first, "b_out");
EXPECT_EQ(info->second, "default");
......@@ -119,7 +133,8 @@ TEST(Settings, user_defined) {
std::runtime_error);
EXPECT_EQ(some_ns::FOLLY_SETTING(follytest, user_defined)->value_, 100);
{
auto info = folly::settings::getAsString("follytest_user_defined");
folly::settings::Snapshot sn;
auto info = sn.getAsString("follytest_user_defined");
EXPECT_TRUE(info.hasValue());
EXPECT_EQ(info->first, "b_out");
EXPECT_EQ(info->second, "default");
......@@ -138,12 +153,13 @@ TEST(Settings, basic) {
a_ns::setRemote(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");
EXPECT_TRUE(res.hasValue());
EXPECT_EQ(res->first, "200");
EXPECT_EQ(res->second, "remote_set");
{
folly::settings::Snapshot sn;
auto res = sn.getAsString("follytest_public_flag_to_a");
EXPECT_TRUE(res.hasValue());
EXPECT_EQ(res->first, "200");
EXPECT_EQ(res->second, "remote_set");
}
{
auto meta = folly::settings::getSettingsMeta("follytest_public_flag_to_a");
EXPECT_TRUE(meta.hasValue());
......@@ -153,7 +169,6 @@ TEST(Settings, basic) {
EXPECT_EQ(md.typeStr, "int");
EXPECT_EQ(md.typeId, typeid(int));
}
{
auto meta = folly::settings::getSettingsMeta("follytest_some_flag");
EXPECT_TRUE(meta.hasValue());
......@@ -163,65 +178,81 @@ TEST(Settings, basic) {
EXPECT_EQ(md.typeStr, "std::string");
EXPECT_EQ(md.typeId, typeid(std::string));
}
res = folly::settings::getAsString("follytest_nonexisting");
EXPECT_FALSE(res.hasValue());
EXPECT_TRUE(folly::settings::setFromString(
"follytest_public_flag_to_a", "300", "from_string"));
EXPECT_EQ(*a_ns::FOLLY_SETTING(follytest, public_flag_to_a), 300);
{
folly::settings::Snapshot sn;
auto res = sn.getAsString("follytest_nonexisting");
EXPECT_FALSE(res.hasValue());
}
{
folly::settings::Snapshot sn;
EXPECT_TRUE(
sn.setFromString("follytest_public_flag_to_a", "300", "from_string"));
sn.publish();
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());
EXPECT_EQ(res->first, "300");
EXPECT_EQ(res->second, "from_string");
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 if (meta.typeId == typeid(unsigned int)) {
EXPECT_EQ(meta.typeStr, "unsigned int");
} else if (meta.typeId == typeid(some_ns::UserDefinedType)) {
EXPECT_EQ(meta.typeStr, "UserDefinedType");
} 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/multi_token_type/unsigned int/123/Test that multi-token type names can be used/123/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"
"follytest/user_defined/UserDefinedType/\"b\"/User defined type constructed from string/b_out/default\n");
EXPECT_TRUE(folly::settings::resetToDefault("follytest_public_flag_to_a"));
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"));
{
folly::settings::Snapshot sn;
auto res = sn.getAsString("follytest_public_flag_to_a");
EXPECT_TRUE(res.hasValue());
EXPECT_EQ(res->first, "300");
EXPECT_EQ(res->second, "from_string");
}
{
folly::settings::Snapshot sn;
EXPECT_FALSE(
sn.setFromString("follytest_nonexisting", "300", "from_string"));
}
{
std::string allFlags;
folly::settings::Snapshot sn;
sn.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 if (meta.typeId == typeid(unsigned int)) {
EXPECT_EQ(meta.typeStr, "unsigned int");
} else if (meta.typeId == typeid(some_ns::UserDefinedType)) {
EXPECT_EQ(meta.typeStr, "UserDefinedType");
} 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/multi_token_type/unsigned int/123/Test that multi-token type names can be used/123/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"
"follytest/user_defined/UserDefinedType/\"b\"/User defined type constructed from string/b_out/default\n");
}
{
folly::settings::Snapshot sn;
EXPECT_TRUE(sn.resetToDefault("follytest_public_flag_to_a"));
sn.publish();
EXPECT_EQ(*a_ns::FOLLY_SETTING(follytest, public_flag_to_a), 456);
EXPECT_EQ(a_ns::getRemote(), 456);
}
{
folly::settings::Snapshot sn;
EXPECT_FALSE(sn.resetToDefault("follytest_nonexisting"));
}
}
TEST(Settings, snapshot) {
......
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