/* * Copyright 2018-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace folly { namespace settings { namespace detail { /** * Can we store T in a global atomic? */ template struct IsSmallPOD : std::integral_constant< bool, std::is_trivial::value && sizeof(T) <= sizeof(uint64_t)> {}; template struct SettingContents { std::string updateReason; T value; template SettingContents(std::string _reason, Args&&... args) : updateReason(std::move(_reason)), value(std::forward(args)...) {} }; class SnapshotBase; class SettingCoreBase { public: using Key = intptr_t; using Version = uint64_t; virtual void setFromString( StringPiece newValue, StringPiece reason, SnapshotBase* snapshot) = 0; virtual std::pair getAsString( const SnapshotBase* snapshot) const = 0; virtual void resetToDefault(SnapshotBase* snapshot) = 0; virtual const SettingMetadata& meta() const = 0; virtual ~SettingCoreBase() {} /** * Hashable key uniquely identifying this setting in this process */ Key getKey() const { return reinterpret_cast(this); } }; void registerSetting(SettingCoreBase& core); /** * Returns the monotonically increasing unique positive version. */ SettingCoreBase::Version nextGlobalVersion(); template class SettingCore; /** * Type erasure for setting values */ class BoxedValue { public: BoxedValue() = default; /** * Stores a value that can be retrieved later */ template explicit BoxedValue(const SettingContents& value) : value_(std::make_shared>(value)) {} /** * Stores a value that can be both retrieved later and optionally * applied globally */ template BoxedValue(const T& value, StringPiece reason, SettingCore& core) : value_(std::make_shared>(reason.str(), value)), publish_([value = value_, &core]() { auto& contents = BoxedValue::unboxImpl(value.get()); core.set(contents.value, contents.updateReason); }) {} /** * Returns the reference to the stored value */ template const SettingContents& unbox() const { return BoxedValue::unboxImpl(value_.get()); } /** * Applies the stored value globally */ void publish() { if (publish_) { publish_(); } } private: std::shared_ptr value_; std::function publish_; template static const SettingContents& unboxImpl(void* value) { return *static_cast*>(value); } }; /** * If there are any outstanding snapshots that care about this * value that's about to be updated, save it to extend its lifetime */ void saveValueForOutstandingSnapshots( SettingCoreBase::Key settingKey, SettingCoreBase::Version version, const BoxedValue& value); /** * @returns a pointer to a saved value at or before the given version */ const BoxedValue* getSavedValue( SettingCoreBase::Key key, SettingCoreBase::Version at); class SnapshotBase { public: /** * Type that encapsulates the current pair of (to(value), reason) */ using SettingsInfo = std::pair; /** * 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 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(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 snapshotValues_; template friend class SettingCore; SnapshotBase(); template const SettingContents& get(const detail::SettingCore& core) const { auto it = snapshotValues_.find(core.getKey()); if (it != snapshotValues_.end()) { return it->second.template unbox(); } auto savedValue = detail::getSavedValue(core.getKey(), at_); if (savedValue) { return savedValue->template unbox(); } return core.getSlow(); } template void set(detail::SettingCore& core, const T& t, StringPiece reason) { snapshotValues_[core.getKey()] = detail::BoxedValue(t, reason, core); } }; template std::enable_if_t::value, T> convertOrConstruct(StringPiece newValue) { return T(newValue); } template std::enable_if_t::value, T> convertOrConstruct(StringPiece newValue) { return to(newValue); } template class SettingCore : public SettingCoreBase { public: using Contents = SettingContents; void setFromString( StringPiece newValue, StringPiece reason, SnapshotBase* snapshot) override { set(convertOrConstruct(newValue), reason.str(), snapshot); } std::pair getAsString( const SnapshotBase* snapshot) const override { auto& contents = snapshot ? snapshot->get(*this) : getSlow(); return std::make_pair( to(contents.value), contents.updateReason); } void resetToDefault(SnapshotBase* snapshot) override { set(defaultValue_, "default", snapshot); } const SettingMetadata& meta() const override { return meta_; } /** * @param trivialStorage must refer to the same location * as the internal trivialStorage_. This hint will * generate better inlined code since the address is known * at compile time at the callsite. */ std::conditional_t::value, T, const T&> getWithHint( std::atomic& trivialStorage) const { return getImpl(IsSmallPOD(), trivialStorage); } const SettingContents& getSlow() const { return *tlValue(); } /*** * SmallPOD version: just read the global atomic */ T getImpl(std::true_type, std::atomic& trivialStorage) const { uint64_t v = trivialStorage.load(); T t; std::memcpy(&t, &v, sizeof(T)); return t; } /** * Non-SmallPOD version: read the thread local shared_ptr */ const T& getImpl(std::false_type, std::atomic& /* ignored */) const { return const_cast(this)->tlValue()->value; } void set(const T& t, StringPiece reason, SnapshotBase* snapshot = nullptr) { /* Check that we can still display it (will throw otherwise) */ to(t); if (snapshot) { snapshot->set(*this, t, reason); return; } SharedMutex::WriteHolder lg(globalLock_); if (globalValue_) { saveValueForOutstandingSnapshots( getKey(), *settingVersion_, BoxedValue(*globalValue_)); } globalValue_ = std::make_shared(reason.str(), t); if (IsSmallPOD::value) { uint64_t v = 0; std::memcpy(&v, &t, sizeof(T)); trivialStorage_.store(v); } *settingVersion_ = nextGlobalVersion(); } const T& defaultValue() const { return defaultValue_; } SettingCore( SettingMetadata meta, T defaultValue, std::atomic& trivialStorage) : meta_(std::move(meta)), defaultValue_(std::move(defaultValue)), trivialStorage_(trivialStorage), localValue_([]() { return new CachelinePadded< std::pair>>(0, nullptr); }) { set(defaultValue_, "default"); registerSetting(*this); } private: SettingMetadata meta_; const T defaultValue_; SharedMutex globalLock_; std::shared_ptr globalValue_; std::atomic& trivialStorage_; /* Thread local versions start at 0, this will force a read on first access. */ CachelinePadded> settingVersion_{1}; ThreadLocal>>> localValue_; FOLLY_ALWAYS_INLINE const std::shared_ptr& tlValue() const { auto& value = **localValue_; if (LIKELY(value.first == *settingVersion_)) { return value.second; } return tlValueSlow(); } FOLLY_NOINLINE const std::shared_ptr& tlValueSlow() const { auto& value = **localValue_; while (value.first < *settingVersion_) { /* If this destroys the old value, do it without holding the lock */ value.second.reset(); SharedMutex::ReadHolder lg(globalLock_); value.first = *settingVersion_; value.second = globalValue_; } return value.second; } }; } // namespace detail } // namespace settings } // namespace folly