diff options
Diffstat (limited to 'absl/flags/internal')
-rw-r--r-- | absl/flags/internal/commandlineflag.cc | 333 | ||||
-rw-r--r-- | absl/flags/internal/commandlineflag.h | 187 | ||||
-rw-r--r-- | absl/flags/internal/flag.cc | 337 | ||||
-rw-r--r-- | absl/flags/internal/flag.h | 237 | ||||
-rw-r--r-- | absl/flags/internal/registry.cc | 45 | ||||
-rw-r--r-- | absl/flags/internal/registry.h | 6 |
6 files changed, 570 insertions, 575 deletions
diff --git a/absl/flags/internal/commandlineflag.cc b/absl/flags/internal/commandlineflag.cc index 99f73611d8f1..caf33bc4892b 100644 --- a/absl/flags/internal/commandlineflag.cc +++ b/absl/flags/internal/commandlineflag.cc @@ -15,14 +15,7 @@ #include "absl/flags/internal/commandlineflag.h" -#include <cassert> - -#include "absl/base/internal/raw_logging.h" -#include "absl/base/optimization.h" -#include "absl/flags/config.h" #include "absl/flags/usage_config.h" -#include "absl/strings/str_cat.h" -#include "absl/synchronization/mutex.h" namespace absl { namespace flags_internal { @@ -35,80 +28,6 @@ namespace flags_internal { // This is used by this file, and also in commandlineflags_reporting.cc const char kStrippedFlagHelp[] = "\001\002\003\004 (unknown) \004\003\002\001"; -namespace { - -// Currently we only validate flag values for user-defined flag types. -bool ShouldValidateFlagValue(const CommandLineFlag& flag) { -#define DONT_VALIDATE(T) \ - if (flag.IsOfType<T>()) return false; - ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(DONT_VALIDATE) - DONT_VALIDATE(std::string) - DONT_VALIDATE(std::vector<std::string>) -#undef DONT_VALIDATE - - return true; -} - -} // namespace - -absl::Mutex* InitFlag(CommandLineFlag* flag) { - ABSL_CONST_INIT static absl::Mutex init_lock(absl::kConstInit); - absl::Mutex* mu; - - { - absl::MutexLock lock(&init_lock); - - if (flag->locks_ == nullptr) { // Must initialize Mutexes for this flag. - flag->locks_ = new flags_internal::CommandLineFlagLocks; - } - - mu = &flag->locks_->primary_mu; - } - - { - absl::MutexLock lock(mu); - - if (!flag->IsRetired() && flag->def_ == nullptr) { - // Need to initialize def and cur fields. - flag->def_ = (*flag->make_init_value_)(); - flag->cur_ = Clone(flag->op_, flag->def_); - UpdateCopy(flag); - flag->inited_.store(true, std::memory_order_release); - flag->InvokeCallback(); - } - } - - flag->inited_.store(true, std::memory_order_release); - return mu; -} - -// Ensure that the lazily initialized fields of *flag have been initialized, -// and return &flag->locks_->primary_mu. -absl::Mutex* CommandLineFlag::InitFlagIfNecessary() const - ABSL_LOCK_RETURNED(locks_->primary_mu) { - if (!inited_.load(std::memory_order_acquire)) { - return InitFlag(const_cast<CommandLineFlag*>(this)); - } - - // All fields initialized; locks_ is therefore safe to read. - return &locks_->primary_mu; -} - -bool CommandLineFlag::IsModified() const { - absl::MutexLock l(InitFlagIfNecessary()); - return modified_; -} - -void CommandLineFlag::SetModified(bool is_modified) { - absl::MutexLock l(InitFlagIfNecessary()); - modified_ = is_modified; -} - -bool CommandLineFlag::IsSpecifiedOnCommandLine() const { - absl::MutexLock l(InitFlagIfNecessary()); - return on_command_line_; -} - absl::string_view CommandLineFlag::Typename() const { // We do not store/report type in Abseil Flags, so that user do not rely on in // at runtime @@ -137,223 +56,6 @@ std::string CommandLineFlag::Filename() const { return flags_internal::GetUsageConfig().normalize_filename(filename_); } -std::string CommandLineFlag::DefaultValue() const { - absl::MutexLock l(InitFlagIfNecessary()); - - return Unparse(marshalling_op_, def_); -} - -std::string CommandLineFlag::CurrentValue() const { - absl::MutexLock l(InitFlagIfNecessary()); - - return Unparse(marshalling_op_, cur_); -} - -int64_t CommandLineFlag::MutationCounter() const { - absl::MutexLock l(InitFlagIfNecessary()); - - return counter_; -} - -// Attempts to parse supplied `value` string using parsing routine in the `flag` -// argument. If parsing is successful, it will try to validate that the parsed -// value is valid for the specified 'flag'. Finally this function stores the -// parsed value in 'dst' assuming it is a pointer to the flag's value type. In -// case if any error is encountered in either step, the error message is stored -// in 'err' -bool TryParseLocked(CommandLineFlag* flag, void* dst, absl::string_view value, - std::string* err) - ABSL_EXCLUSIVE_LOCKS_REQUIRED(flag->locks_->primary_mu) { - void* tentative_value = Clone(flag->op_, flag->def_); - std::string parse_err; - if (!Parse(flag->marshalling_op_, value, tentative_value, &parse_err)) { - auto type_name = flag->Typename(); - absl::string_view err_sep = parse_err.empty() ? "" : "; "; - absl::string_view typename_sep = type_name.empty() ? "" : " "; - *err = absl::StrCat("Illegal value '", value, "' specified for", - typename_sep, type_name, " flag '", flag->Name(), "'", - err_sep, parse_err); - Delete(flag->op_, tentative_value); - return false; - } - - if (!flag->InvokeValidator(tentative_value)) { - *err = absl::StrCat("Failed validation of new value '", - Unparse(flag->marshalling_op_, tentative_value), - "' for flag '", flag->Name(), "'"); - Delete(flag->op_, tentative_value); - return false; - } - - flag->counter_++; - Copy(flag->op_, tentative_value, dst); - Delete(flag->op_, tentative_value); - return true; -} - -// Sets the value of the flag based on specified string `value`. If the flag -// was successfully set to new value, it returns true. Otherwise, sets `err` -// to indicate the error, leaves the flag unchanged, and returns false. There -// are three ways to set the flag's value: -// * Update the current flag value -// * Update the flag's default value -// * Update the current flag value if it was never set before -// The mode is selected based on 'set_mode' parameter. -bool CommandLineFlag::SetFromString(absl::string_view value, - FlagSettingMode set_mode, - ValueSource source, std::string* err) { - if (IsRetired()) return false; - - absl::MutexLock l(InitFlagIfNecessary()); - - // Direct-access flags can be modified without going through the - // flag API. Detect such changes and update the flag->modified_ bit. - if (!IsAbseilFlag()) { - if (!modified_ && ChangedDirectly(this, cur_, def_)) { - modified_ = true; - } - } - - switch (set_mode) { - case SET_FLAGS_VALUE: { - // set or modify the flag's value - if (!TryParseLocked(this, cur_, value, err)) return false; - modified_ = true; - UpdateCopy(this); - InvokeCallback(); - - if (source == kCommandLine) { - on_command_line_ = true; - } - break; - } - case SET_FLAG_IF_DEFAULT: { - // set the flag's value, but only if it hasn't been set by someone else - if (!modified_) { - if (!TryParseLocked(this, cur_, value, err)) return false; - modified_ = true; - UpdateCopy(this); - InvokeCallback(); - } else { - // TODO(rogeeff): review and fix this semantic. Currently we do not fail - // in this case if flag is modified. This is misleading since the flag's - // value is not updated even though we return true. - // *err = absl::StrCat(Name(), " is already set to ", - // CurrentValue(), "\n"); - // return false; - return true; - } - break; - } - case SET_FLAGS_DEFAULT: { - // modify the flag's default-value - if (!TryParseLocked(this, def_, value, err)) return false; - - if (!modified_) { - // Need to set both defvalue *and* current, in this case - Copy(op_, def_, cur_); - UpdateCopy(this); - InvokeCallback(); - } - break; - } - default: { - // unknown set_mode - assert(false); - return false; - } - } - - return true; -} - -void CommandLineFlag::CheckDefaultValueParsingRoundtrip() const { - std::string v = DefaultValue(); - - absl::MutexLock lock(InitFlagIfNecessary()); - - void* dst = Clone(op_, def_); - std::string error; - if (!flags_internal::Parse(marshalling_op_, v, dst, &error)) { - ABSL_INTERNAL_LOG( - FATAL, - absl::StrCat("Flag ", Name(), " (from ", Filename(), - "): std::string form of default value '", v, - "' could not be parsed; error=", error)); - } - - // We do not compare dst to def since parsing/unparsing may make - // small changes, e.g., precision loss for floating point types. - Delete(op_, dst); -} - -bool CommandLineFlag::ValidateDefaultValue() const { - absl::MutexLock lock(InitFlagIfNecessary()); - return InvokeValidator(def_); -} - -bool CommandLineFlag::ValidateInputValue(absl::string_view value) const { - absl::MutexLock l(InitFlagIfNecessary()); // protect default value access - - void* obj = Clone(op_, def_); - std::string ignored_error; - const bool result = - flags_internal::Parse(marshalling_op_, value, obj, &ignored_error) && - InvokeValidator(obj); - Delete(op_, obj); - return result; -} - -void CommandLineFlag::Read(void* dst, - const flags_internal::FlagOpFn dst_op) const { - absl::ReaderMutexLock l(InitFlagIfNecessary()); - - // `dst_op` is the unmarshaling operation corresponding to the declaration - // visibile at the call site. `op` is the Flag's defined unmarshalling - // operation. They must match for this operation to be well-defined. - if (ABSL_PREDICT_FALSE(dst_op != op_)) { - ABSL_INTERNAL_LOG( - ERROR, - absl::StrCat("Flag '", Name(), - "' is defined as one type and declared as another")); - } - CopyConstruct(op_, cur_, dst); -} - -void CommandLineFlag::Write(const void* src, - const flags_internal::FlagOpFn src_op) { - absl::MutexLock l(InitFlagIfNecessary()); - - // `src_op` is the marshalling operation corresponding to the declaration - // visible at the call site. `op` is the Flag's defined marshalling operation. - // They must match for this operation to be well-defined. - if (ABSL_PREDICT_FALSE(src_op != op_)) { - ABSL_INTERNAL_LOG( - ERROR, - absl::StrCat("Flag '", Name(), - "' is defined as one type and declared as another")); - } - - if (ShouldValidateFlagValue(*this)) { - void* obj = Clone(op_, src); - std::string ignored_error; - std::string src_as_str = Unparse(marshalling_op_, src); - if (!Parse(marshalling_op_, src_as_str, obj, &ignored_error) || - !InvokeValidator(obj)) { - ABSL_INTERNAL_LOG(ERROR, absl::StrCat("Attempt to set flag '", Name(), - "' to invalid value ", src_as_str)); - } - Delete(op_, obj); - } - - modified_ = true; - counter_++; - Copy(op_, src, cur_); - - UpdateCopy(this); - InvokeCallback(); -} - std::string HelpText::GetHelpText() const { if (help_function_) return help_function_(); if (help_message_) return help_message_; @@ -361,40 +63,5 @@ std::string HelpText::GetHelpText() const { return {}; } -// Update any copy of the flag value that is stored in an atomic word. -// In addition if flag has a mutation callback this function invokes it. -void UpdateCopy(CommandLineFlag* flag) { -#define STORE_ATOMIC(T) \ - else if (flag->IsOfType<T>()) { \ - flag->StoreAtomic(); \ - } - - if (false) { - } - ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(STORE_ATOMIC) -#undef STORE_ATOMIC -} - -// Return true iff flag value was changed via direct-access. -bool ChangedDirectly(CommandLineFlag* flag, const void* a, const void* b) { - if (!flag->IsAbseilFlag()) { -// Need to compare values for direct-access flags. -#define CHANGED_FOR_TYPE(T) \ - if (flag->IsOfType<T>()) { \ - return *reinterpret_cast<const T*>(a) != *reinterpret_cast<const T*>(b); \ - } - - CHANGED_FOR_TYPE(bool); - CHANGED_FOR_TYPE(int32_t); - CHANGED_FOR_TYPE(int64_t); - CHANGED_FOR_TYPE(uint64_t); - CHANGED_FOR_TYPE(double); - CHANGED_FOR_TYPE(std::string); -#undef CHANGED_FOR_TYPE - } - - return false; -} - } // namespace flags_internal } // namespace absl diff --git a/absl/flags/internal/commandlineflag.h b/absl/flags/internal/commandlineflag.h index 528d3106bfa8..13a3352e80f4 100644 --- a/absl/flags/internal/commandlineflag.h +++ b/absl/flags/internal/commandlineflag.h @@ -16,12 +16,10 @@ #ifndef ABSL_FLAGS_INTERNAL_COMMANDLINEFLAG_H_ #define ABSL_FLAGS_INTERNAL_COMMANDLINEFLAG_H_ -#include <atomic> #include <memory> #include "absl/base/macros.h" #include "absl/flags/marshalling.h" -#include "absl/synchronization/mutex.h" #include "absl/types/optional.h" namespace absl { @@ -151,14 +149,6 @@ inline size_t Sizeof(FlagOpFn op) { op(flags_internal::kSizeof, nullptr, nullptr))); } -// The following struct contains the locks in a CommandLineFlag struct. -// They are in a separate struct that is lazily allocated to avoid problems -// with static initialization and to avoid multiple allocations. -struct CommandLineFlagLocks { - absl::Mutex primary_mu; // protects several fields in CommandLineFlag - absl::Mutex callback_mu; // used to serialize callbacks -}; - // Holds either a pointer to help text or a function which produces it. This is // needed for supporting both static initialization of Flags while supporting // the legacy registration framework. We can't use absl::variant<const char*, @@ -200,25 +190,9 @@ class FlagStateInterface { // Holds all information for a flag. class CommandLineFlag { public: - constexpr CommandLineFlag( - const char* name, HelpText help_text, const char* filename, - const flags_internal::FlagOpFn op, - const flags_internal::FlagMarshallingOpFn marshalling_op, - const flags_internal::InitialValGenFunc initial_value_gen, void* def, - void* cur) - : name_(name), - help_(help_text), - filename_(filename), - op_(op), - marshalling_op_(marshalling_op), - make_init_value_(initial_value_gen), - inited_(false), - modified_(false), - on_command_line_(false), - def_(def), - cur_(cur), - counter_(0), - locks_(nullptr) {} + constexpr CommandLineFlag(const char* name, HelpText help_text, + const char* filename) + : name_(name), help_(help_text), filename_(filename) {} // Virtual destructor virtual void Destroy() const = 0; @@ -227,60 +201,73 @@ class CommandLineFlag { CommandLineFlag(const CommandLineFlag&) = delete; CommandLineFlag& operator=(const CommandLineFlag&) = delete; - // Access methods. - - // Returns true iff this object corresponds to retired flag - virtual bool IsRetired() const { return false; } - // Returns true iff this is a handle to an Abseil Flag. - virtual bool IsAbseilFlag() const { return true; } - + // Non-polymorphic access methods. absl::string_view Name() const { return name_; } std::string Help() const { return help_.GetHelpText(); } - bool IsModified() const; - void SetModified(bool is_modified); - bool IsSpecifiedOnCommandLine() const; - absl::string_view Typename() const; std::string Filename() const; - std::string DefaultValue() const; - std::string CurrentValue() const; - - // Interface to store the value in atomic if one used. This is short term - // interface. To be reworked once cur_ is moved. - virtual void StoreAtomic() {} - - // Interfaces to operate on validators. - virtual bool InvokeValidator(const void* /*value*/) const { return true; } - // Invoke the flag validators for old flags. - // TODO(rogeeff): implement proper validators for Abseil Flags - bool ValidateDefaultValue() const; - bool ValidateInputValue(absl::string_view value) const; // Return true iff flag has type T. template <typename T> inline bool IsOfType() const { - return op_ == &flags_internal::FlagOps<T>; + return TypeId() == &flags_internal::FlagOps<T>; } // Attempts to retrieve the flag value. Returns value on success, // absl::nullopt otherwise. template <typename T> absl::optional<T> Get() const { - if (IsRetired() || flags_internal::FlagOps<T> != op_) return absl::nullopt; - - T res; - Read(&res, flags_internal::FlagOps<T>); + if (IsRetired() || !IsOfType<T>()) { + return absl::nullopt; + } - return res; + // Implementation notes: + // + // We are wrapping a union around the value of `T` to serve three purposes: + // + // 1. `U.value` has correct size and alignment for a value of type `T` + // 2. The `U.value` constructor is not invoked since U's constructor does + // not + // do it explicitly. + // 3. The `U.value` destructor is invoked since U's destructor does it + // explicitly. This makes `U` a kind of RAII wrapper around non default + // constructible value of T, which is destructed when we leave the + // scope. We do need to destroy U.value, which is constructed by + // CommandLineFlag::Read even though we left it in a moved-from state + // after std::move. + // + // All of this serves to avoid requiring `T` being default constructible. + union U { + T value; + U() {} + ~U() { value.~T(); } + }; + U u; + + Read(&u.value); + return std::move(u.value); } + // Polymorphic access methods + + // Returns true iff this object corresponds to retired flag + virtual bool IsRetired() const { return false; } + // Returns true iff this is a handle to an Abseil Flag. + virtual bool IsAbseilFlag() const { return true; } + // Returns id of the flag's value type. + virtual flags_internal::FlagOpFn TypeId() const = 0; + virtual bool IsModified() const = 0; + virtual bool IsSpecifiedOnCommandLine() const = 0; + virtual std::string DefaultValue() const = 0; + virtual std::string CurrentValue() const = 0; + + // Interfaces to operate on validators. + virtual bool ValidateInputValue(absl::string_view value) const = 0; + // Interface to save flag to some persistent state. Returns current flag state // or nullptr if flag does not support saving and restoring a state. virtual std::unique_ptr<FlagStateInterface> SaveState() = 0; - // Interfaces to overate on callbacks. - virtual void InvokeCallback() {} - // Sets the value of the flag based on specified std::string `value`. If the flag // was successfully set to new value, it returns true. Otherwise, sets `error` // to indicate the error, leaves the flag unchanged, and returns false. There @@ -289,74 +276,28 @@ class CommandLineFlag { // * Update the flag's default value // * Update the current flag value if it was never set before // The mode is selected based on `set_mode` parameter. - bool SetFromString(absl::string_view value, - flags_internal::FlagSettingMode set_mode, - flags_internal::ValueSource source, std::string* error); + virtual bool SetFromString(absl::string_view value, + flags_internal::FlagSettingMode set_mode, + flags_internal::ValueSource source, + std::string* error) = 0; - void CheckDefaultValueParsingRoundtrip() const; + // Checks that flags default value can be converted to std::string and back to the + // flag's value type. + virtual void CheckDefaultValueParsingRoundtrip() const = 0; // Constant configuration for a particular flag. protected: ~CommandLineFlag() = default; - // Thread safe access to mutation counter. - int64_t MutationCounter() const; - - const char* const name_; - const HelpText help_; - const char* const filename_; - - const FlagOpFn op_; // Type-specific handler - const FlagMarshallingOpFn marshalling_op_; // Marshalling ops handler - const InitialValGenFunc make_init_value_; // Makes initial value for the flag - std::atomic<bool> inited_; // fields have been lazily initialized - - // Mutable state (guarded by locks_->primary_mu). - bool modified_; // Has flag value been modified? - bool on_command_line_; // Specified on command line. - void* def_; // Lazily initialized pointer to default value - void* cur_; // Lazily initialized pointer to current value - int64_t counter_; // Mutation counter - - // Lazily initialized mutexes for this flag value. We cannot inline a - // SpinLock or Mutex here because those have non-constexpr constructors and - // so would prevent constant initialization of this type. - // TODO(rogeeff): fix it once Mutex has constexpr constructor - struct CommandLineFlagLocks* locks_; // locks, laziliy allocated. - - // Ensure that the lazily initialized fields of *flag have been initialized, - // and return the lock which should be locked when flag's state is mutated. - absl::Mutex* InitFlagIfNecessary() const ABSL_LOCK_RETURNED(locks_->primary_mu); - - // copy construct new value of flag's type in a memory referenced by dst - // based on current flag's value - void Read(void* dst, const flags_internal::FlagOpFn dst_op) const; - // updates flag's value to *src (locked) - void Write(const void* src, const flags_internal::FlagOpFn src_op); - - friend class FlagRegistry; - friend class FlagPtrMap; - friend class FlagSaverImpl; - friend bool TryParseLocked(CommandLineFlag* flag, void* dst, - absl::string_view value, std::string* err); - friend absl::Mutex* InitFlag(CommandLineFlag* flag); -}; + const char* const name_; // Flags name passed to ABSL_FLAG as second arg. + const HelpText help_; // The function generating help message. + const char* const filename_; // The file name where ABSL_FLAG resides. -// Update any copy of the flag value that is stored in an atomic word. -// In addition if flag has a mutation callback this function invokes it. While -// callback is being invoked the primary flag's mutex is unlocked and it is -// re-locked back after call to callback is completed. Callback invocation is -// guarded by flag's secondary mutex instead which prevents concurrent callback -// invocation. Note that it is possible for other thread to grab the primary -// lock and update flag's value at any time during the callback invocation. -// This is by design. Callback can get a value of the flag if necessary, but it -// might be different from the value initiated the callback and it also can be -// different by the time the callback invocation is completed. -// Requires that *primary_lock be held in exclusive mode; it may be released -// and reacquired by the implementation. -void UpdateCopy(CommandLineFlag* flag); -// Return true iff flag value was changed via direct-access. -bool ChangedDirectly(CommandLineFlag* flag, const void* a, const void* b); + private: + // Copy-construct a new value of the flag's type in a memory referenced by + // the dst based on the current flag's value. + virtual void Read(void* dst) const = 0; +}; // This macro is the "source of truth" for the list of supported flag types we // expect to perform lock free operations on. Specifically it generates code, diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index 0f4035819cdb..061113d76557 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc @@ -15,36 +15,331 @@ #include "absl/flags/internal/flag.h" +#include "absl/base/optimization.h" #include "absl/synchronization/mutex.h" namespace absl { namespace flags_internal { +namespace { -// If the flag has a mutation callback this function invokes it. While the -// callback is being invoked the primary flag's mutex is unlocked and it is -// re-locked back after call to callback is completed. Callback invocation is -// guarded by flag's secondary mutex instead which prevents concurrent -// callback invocation. Note that it is possible for other thread to grab the -// primary lock and update flag's value at any time during the callback -// invocation. This is by design. Callback can get a value of the flag if -// necessary, but it might be different from the value initiated the callback -// and it also can be different by the time the callback invocation is -// completed. Requires that *primary_lock be held in exclusive mode; it may be -// released and reacquired by the implementation. -void InvokeCallback(absl::Mutex* primary_mu, absl::Mutex* callback_mu, - FlagCallback cb) ABSL_EXCLUSIVE_LOCKS_REQUIRED(primary_mu) { - if (!cb) return; - - // When executing the callback we need the primary flag's mutex to be - // unlocked so that callback can retrieve the flag's value. - primary_mu->Unlock(); +// Currently we only validate flag values for user-defined flag types. +bool ShouldValidateFlagValue(const CommandLineFlag& flag) { +#define DONT_VALIDATE(T) \ + if (flag.IsOfType<T>()) return false; + ABSL_FLAGS_INTERNAL_FOR_EACH_LOCK_FREE(DONT_VALIDATE) + DONT_VALIDATE(std::string) + DONT_VALIDATE(std::vector<std::string>) +#undef DONT_VALIDATE + return true; +} + +} // namespace + +void FlagImpl::Init() { + ABSL_CONST_INIT static absl::Mutex init_lock(absl::kConstInit); + + { + absl::MutexLock lock(&init_lock); + + if (locks_ == nullptr) { // Must initialize Mutexes for this flag. + locks_ = new FlagImpl::CommandLineFlagLocks; + } + } + + absl::MutexLock lock(&locks_->primary_mu); + + if (def_ != nullptr) { + inited_.store(true, std::memory_order_release); + } else { + // Need to initialize def and cur fields. + def_ = (*initial_value_gen_)(); + cur_ = Clone(op_, def_); + StoreAtomic(); + inited_.store(true, std::memory_order_release); + InvokeCallback(); + } +} + +// Ensures that the lazily initialized data is initialized, +// and returns pointer to the mutex guarding flags data. +absl::Mutex* FlagImpl::DataGuard() const + ABSL_LOCK_RETURNED(locks_->primary_mu) { + if (ABSL_PREDICT_FALSE(!inited_.load(std::memory_order_acquire))) { + const_cast<FlagImpl*>(this)->Init(); + } + + // All fields initialized; locks_ is therefore safe to read. + return &locks_->primary_mu; +} + +void FlagImpl::Destroy() const { { - absl::MutexLock lock(callback_mu); - cb(); + absl::MutexLock l(DataGuard()); + + // Values are heap allocated for Abseil Flags. + if (cur_) Delete(op_, cur_); + if (def_) Delete(op_, def_); } - primary_mu->Lock(); + delete locks_; +} + +bool FlagImpl::IsModified() const { + absl::MutexLock l(DataGuard()); + return modified_; +} + +bool FlagImpl::IsSpecifiedOnCommandLine() const { + absl::MutexLock l(DataGuard()); + return on_command_line_; +} + +std::string FlagImpl::DefaultValue() const { + absl::MutexLock l(DataGuard()); + + return Unparse(marshalling_op_, def_); +} + +std::string FlagImpl::CurrentValue() const { + absl::MutexLock l(DataGuard()); + + return Unparse(marshalling_op_, cur_); +} + +void FlagImpl::SetCallback( + const flags_internal::FlagCallback mutation_callback) { + absl::MutexLock l(DataGuard()); + + callback_ = mutation_callback; + + InvokeCallback(); +} + +void FlagImpl::InvokeCallback() const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(locks_->primary_mu) { + if (!callback_) return; + + // If the flag has a mutation callback this function invokes it. While the + // callback is being invoked the primary flag's mutex is unlocked and it is + // re-locked back after call to callback is completed. Callback invocation is + // guarded by flag's secondary mutex instead which prevents concurrent + // callback invocation. Note that it is possible for other thread to grab the + // primary lock and update flag's value at any time during the callback + // invocation. This is by design. Callback can get a value of the flag if + // necessary, but it might be different from the value initiated the callback + // and it also can be different by the time the callback invocation is + // completed. Requires that *primary_lock be held in exclusive mode; it may be + // released and reacquired by the implementation. + DataGuard()->Unlock(); + + { + absl::MutexLock lock(&locks_->callback_mu); + callback_(); + } + + DataGuard()->Lock(); +} + +bool FlagImpl::RestoreState(const CommandLineFlag& flag, const void* value, + bool modified, bool on_command_line, + int64_t counter) { + { + absl::MutexLock l(DataGuard()); + + if (counter_ == counter) return false; + } + + Write(flag, value, op_); + + { + absl::MutexLock l(DataGuard()); + + modified_ = modified; + on_command_line_ = on_command_line; + } + + return true; +} + +// Attempts to parse supplied `value` string using parsing routine in the `flag` +// argument. If parsing successful, this function stores the parsed value in +// 'dst' assuming it is a pointer to the flag's value type. In case if any error +// is encountered in either step, the error message is stored in 'err' +bool FlagImpl::TryParse(const CommandLineFlag& flag, void* dst, + absl::string_view value, std::string* err) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(locks_->primary_mu) { + void* tentative_value = Clone(op_, def_); + std::string parse_err; + if (!Parse(marshalling_op_, value, tentative_value, &parse_err)) { + auto type_name = flag.Typename(); + absl::string_view err_sep = parse_err.empty() ? "" : "; "; + absl::string_view typename_sep = type_name.empty() ? "" : " "; + *err = absl::StrCat("Illegal value '", value, "' specified for", + typename_sep, type_name, " flag '", flag.Name(), "'", + err_sep, parse_err); + Delete(op_, tentative_value); + return false; + } + + Copy(op_, tentative_value, dst); + Delete(op_, tentative_value); + return true; +} + +void FlagImpl::Read(const CommandLineFlag& flag, void* dst, + const flags_internal::FlagOpFn dst_op) const { + absl::ReaderMutexLock l(DataGuard()); + + // `dst_op` is the unmarshaling operation corresponding to the declaration + // visibile at the call site. `op` is the Flag's defined unmarshalling + // operation. They must match for this operation to be well-defined. + if (ABSL_PREDICT_FALSE(dst_op != op_)) { + ABSL_INTERNAL_LOG( + ERROR, + absl::StrCat("Flag '", flag.Name(), + "' is defined as one type and declared as another")); + } + CopyConstruct(op_, cur_, dst); +} + +void FlagImpl::StoreAtomic() ABSL_EXCLUSIVE_LOCKS_REQUIRED(locks_->primary_mu) { + size_t data_size = Sizeof(op_); + + if (data_size <= sizeof(int64_t)) { + int64_t t = 0; + std::memcpy(&t, cur_, data_size); + atomic_.store(t, std::memory_order_release); + } +} + +void FlagImpl::Write(const CommandLineFlag& flag, const void* src, + const flags_internal::FlagOpFn src_op) { + absl::MutexLock l(DataGuard()); + + // `src_op` is the marshalling operation corresponding to the declaration + // visible at the call site. `op` is the Flag's defined marshalling operation. + // They must match for this operation to be well-defined. + if (ABSL_PREDICT_FALSE(src_op != op_)) { + ABSL_INTERNAL_LOG( + ERROR, + absl::StrCat("Flag '", flag.Name(), + "' is defined as one type and declared as another")); + } + + if (ShouldValidateFlagValue(flag)) { + void* obj = Clone(op_, src); + std::string ignored_error; + std::string src_as_str = Unparse(marshalling_op_, src); + if (!Parse(marshalling_op_, src_as_str, obj, &ignored_error)) { + ABSL_INTERNAL_LOG(ERROR, + absl::StrCat("Attempt to set flag '", flag.Name(), + "' to invalid value ", src_as_str)); + } + Delete(op_, obj); + } + + modified_ = true; + counter_++; + Copy(op_, src, cur_); + + StoreAtomic(); + InvokeCallback(); +} + +// Sets the value of the flag based on specified string `value`. If the flag +// was successfully set to new value, it returns true. Otherwise, sets `err` +// to indicate the error, leaves the flag unchanged, and returns false. There +// are three ways to set the flag's value: +// * Update the current flag value +// * Update the flag's default value +// * Update the current flag value if it was never set before +// The mode is selected based on 'set_mode' parameter. +bool FlagImpl::SetFromString(const CommandLineFlag& flag, + absl::string_view value, FlagSettingMode set_mode, + ValueSource source, std::string* err) { + absl::MutexLock l(DataGuard()); + + switch (set_mode) { + case SET_FLAGS_VALUE: { + // set or modify the flag's value + if (!TryParse(flag, cur_, value, err)) return false; + modified_ = true; + counter_++; + StoreAtomic(); + InvokeCallback(); + + if (source == kCommandLine) { + on_command_line_ = true; + } + break; + } + case SET_FLAG_IF_DEFAULT: { + // set the flag's value, but only if it hasn't been set by someone else + if (!modified_) { + if (!TryParse(flag, cur_, value, err)) return false; + modified_ = true; + counter_++; + StoreAtomic(); + InvokeCallback(); + } else { + // TODO(rogeeff): review and fix this semantic. Currently we do not fail + // in this case if flag is modified. This is misleading since the flag's + // value is not updated even though we return true. + // *err = absl::StrCat(Name(), " is already set to ", + // CurrentValue(), "\n"); + // return false; + return true; + } + break; + } + case SET_FLAGS_DEFAULT: { + // modify the flag's default-value + if (!TryParse(flag, def_, value, err)) return false; + + if (!modified_) { + // Need to set both default value *and* current, in this case + Copy(op_, def_, cur_); + StoreAtomic(); + InvokeCallback(); + } + break; + } + } + + return true; +} + +void FlagImpl::CheckDefaultValueParsingRoundtrip( + const CommandLineFlag& flag) const { + std::string v = DefaultValue(); + + absl::MutexLock lock(DataGuard()); + + void* dst = Clone(op_, def_); + std::string error; + if (!flags_internal::Parse(marshalling_op_, v, dst, &error)) { + ABSL_INTERNAL_LOG( + FATAL, + absl::StrCat("Flag ", flag.Name(), " (from ", flag.Filename(), + "): std::string form of default value '", v, + "' could not be parsed; error=", error)); + } + + // We do not compare dst to def since parsing/unparsing may make + // small changes, e.g., precision loss for floating point types. + Delete(op_, dst); +} + +bool FlagImpl::ValidateInputValue(absl::string_view value) const { + absl::MutexLock l(DataGuard()); + + void* obj = Clone(op_, def_); + std::string ignored_error; + const bool result = + flags_internal::Parse(marshalling_op_, value, obj, &ignored_error); + Delete(op_, obj); + return result; } } // namespace flags_internal diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index 2b21c4407363..ce0ccf2d31d2 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -16,12 +16,15 @@ #ifndef ABSL_FLAGS_INTERNAL_FLAG_H_ #define ABSL_FLAGS_INTERNAL_FLAG_H_ +#include <atomic> #include <cstring> +#include "absl/base/thread_annotations.h" #include "absl/flags/internal/commandlineflag.h" #include "absl/flags/internal/registry.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" +#include "absl/synchronization/mutex.h" namespace absl { namespace flags_internal { @@ -66,6 +69,124 @@ using FlagCallback = void (*)(); void InvokeCallback(absl::Mutex* primary_mu, absl::Mutex* callback_mu, FlagCallback cb) ABSL_EXCLUSIVE_LOCKS_REQUIRED(primary_mu); +// The class encapsulates the Flag's data and safe access to it. +class FlagImpl { + public: + constexpr FlagImpl(const flags_internal::FlagOpFn op, + const flags_internal::FlagMarshallingOpFn marshalling_op, + const flags_internal::InitialValGenFunc initial_value_gen) + : op_(op), + marshalling_op_(marshalling_op), + initial_value_gen_(initial_value_gen) {} + + // Forces destruction of the Flag's data. + void Destroy() const; + + // Constant access methods + bool IsModified() const ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + bool IsSpecifiedOnCommandLine() const ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + std::string DefaultValue() const ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + std::string CurrentValue() const ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + void Read(const CommandLineFlag& flag, void* dst, + const flags_internal::FlagOpFn dst_op) const + ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + // Attempts to parse supplied `value` std::string. + bool TryParse(const CommandLineFlag& flag, void* dst, absl::string_view value, + std::string* err) const + ABSL_EXCLUSIVE_LOCKS_REQUIRED(locks_->primary_mu); + template <typename T> + bool AtomicGet(T* v) const { + const int64_t r = atomic_.load(std::memory_order_acquire); + if (r != flags_internal::AtomicInit()) { + std::memcpy(v, &r, sizeof(T)); + return true; + } + + return false; + } + + // Mutating access methods + void Write(const CommandLineFlag& flag, const void* src, + const flags_internal::FlagOpFn src_op) + ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + bool SetFromString(const CommandLineFlag& flag, absl::string_view value, + FlagSettingMode set_mode, ValueSource source, + std::string* err) ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + // If possible, updates copy of the Flag's value that is stored in an + // atomic word. + void StoreAtomic() ABSL_EXCLUSIVE_LOCKS_REQUIRED(locks_->primary_mu); + + // Interfaces to operate on callbacks. + void SetCallback(const flags_internal::FlagCallback mutation_callback) + ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + void InvokeCallback() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(locks_->primary_mu); + + // Interfaces to save/restore mutable flag data + template <typename T> + std::unique_ptr<flags_internal::FlagStateInterface> SaveState( + Flag<T>* flag) const ABSL_LOCKS_EXCLUDED(locks_->primary_mu) { + T&& cur_value = flag->Get(); + absl::MutexLock l(DataGuard()); + + return absl::make_unique<flags_internal::FlagState<T>>( + flag, std::move(cur_value), modified_, on_command_line_, counter_); + } + bool RestoreState(const CommandLineFlag& flag, const void* value, + bool modified, bool on_command_line, int64_t counter) + ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + + // Value validation interfaces. + void CheckDefaultValueParsingRoundtrip(const CommandLineFlag& flag) const + ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + bool ValidateInputValue(absl::string_view value) const + ABSL_LOCKS_EXCLUDED(locks_->primary_mu); + + private: + // Lazy initialization of the Flag's data. + void Init(); + // Ensures that the lazily initialized data is initialized, + // and returns pointer to the mutex guarding flags data. + absl::Mutex* DataGuard() const ABSL_LOCK_RETURNED(locks_->primary_mu); + + // Immutable Flag's data. + const FlagOpFn op_; // Type-specific handler + const FlagMarshallingOpFn marshalling_op_; // Marshalling ops handler + const InitialValGenFunc initial_value_gen_; // Makes flag's initial value + + // Mutable Flag's data. (guarded by locks_->primary_mu). + // Indicates that locks_, cur_ and def_ fields have been lazily initialized. + std::atomic<bool> inited_{false}; + // Has flag value been modified? + bool modified_ ABSL_GUARDED_BY(locks_->primary_mu) = false; + // Specified on command line. + bool on_command_line_ ABSL_GUARDED_BY(locks_->primary_mu) = false; + // Lazily initialized pointer to default value + void* def_ ABSL_GUARDED_BY(locks_->primary_mu) = nullptr; + // Lazily initialized pointer to current value + void* cur_ ABSL_GUARDED_BY(locks_->primary_mu) = nullptr; + // Mutation counter + int64_t counter_ ABSL_GUARDED_BY(locks_->primary_mu) = 0; + // For some types, a copy of the current value is kept in an atomically + // accessible field. + std::atomic<int64_t> atomic_{flags_internal::AtomicInit()}; + // Mutation callback + FlagCallback callback_ = nullptr; + + // Lazily initialized mutexes for this flag value. We cannot inline a + // SpinLock or Mutex here because those have non-constexpr constructors and + // so would prevent constant initialization of this type. + // TODO(rogeeff): fix it once Mutex has constexpr constructor + // The following struct contains the locks in a CommandLineFlag struct. + // They are in a separate struct that is lazily allocated to avoid problems + // with static initialization and to avoid multiple allocations. + struct CommandLineFlagLocks { + absl::Mutex primary_mu; // protects several fields in CommandLineFlag + absl::Mutex callback_mu; // used to serialize callbacks + }; + + CommandLineFlagLocks* locks_ = nullptr; // locks, laziliy allocated. +}; + // This is "unspecified" implementation of absl::Flag<T> type. template <typename T> class Flag final : public flags_internal::CommandLineFlag { @@ -76,30 +197,11 @@ class Flag final : public flags_internal::CommandLineFlag { const flags_internal::InitialValGenFunc initial_value_gen) : flags_internal::CommandLineFlag( name, flags_internal::HelpText::FromFunctionPointer(help_gen), - filename, &flags_internal::FlagOps<T>, marshalling_op, - initial_value_gen, - /*def=*/nullptr, - /*cur=*/nullptr), - atomic_(flags_internal::AtomicInit()), - callback_(nullptr) {} + filename), + impl_(&flags_internal::FlagOps<T>, marshalling_op, initial_value_gen) {} T Get() const { - // Implementation notes: - // - // We are wrapping a union around the value of `T` to serve three purposes: - // - // 1. `U.value` has correct size and alignment for a value of type `T` - // 2. The `U.value` constructor is not invoked since U's constructor does - // not - // do it explicitly. - // 3. The `U.value` destructor is invoked since U's destructor does it - // explicitly. This makes `U` a kind of RAII wrapper around non default - // constructible value of T, which is destructed when we leave the - // scope. We do need to destroy U.value, which is constructed by - // CommandLineFlag::Read even though we left it in a moved-from state - // after std::move. - // - // All of this serves to avoid requiring `T` being default constructible. + // See implementation notes in CommandLineFlag::Get(). union U { T value; U() {} @@ -107,89 +209,70 @@ class Flag final : public flags_internal::CommandLineFlag { }; U u; - Read(&u.value, &flags_internal::FlagOps<T>); + impl_.Read(*this, &u.value, &flags_internal::FlagOps<T>); return std::move(u.value); } - bool AtomicGet(T* v) const { - const int64_t r = atomic_.load(std::memory_order_acquire); - if (r != flags_internal::AtomicInit()) { - std::memcpy(v, &r, sizeof(T)); - return true; - } - - return false; - } + bool AtomicGet(T* v) const { return impl_.AtomicGet(v); } - void Set(const T& v) { Write(&v, &flags_internal::FlagOps<T>); } + void Set(const T& v) { impl_.Write(*this, &v, &flags_internal::FlagOps<T>); } void SetCallback(const flags_internal::FlagCallback mutation_callback) { - absl::MutexLock l(InitFlagIfNecessary()); - - callback_ = mutation_callback; - - InvokeCallback(); + impl_.SetCallback(mutation_callback); } - private: - friend class FlagState<T>; - - void Destroy() const override { - // Values are heap allocated for Abseil Flags. - if (cur_) Delete(op_, cur_); - if (def_) Delete(op_, def_); - - delete locks_; + // CommandLineFlag interface + bool IsModified() const override { return impl_.IsModified(); } + bool IsSpecifiedOnCommandLine() const override { + return impl_.IsSpecifiedOnCommandLine(); } + std::string DefaultValue() const override { return impl_.DefaultValue(); } + std::string CurrentValue() const override { return impl_.CurrentValue(); } - void StoreAtomic() override { - if (sizeof(T) <= sizeof(int64_t)) { - int64_t t = 0; - std::memcpy(&t, cur_, (std::min)(sizeof(T), sizeof(int64_t))); - atomic_.store(t, std::memory_order_release); - } + bool ValidateInputValue(absl::string_view value) const override { + return impl_.ValidateInputValue(value); } // Interfaces to save and restore flags to/from persistent state. // Returns current flag state or nullptr if flag does not support // saving and restoring a state. std::unique_ptr<flags_internal::FlagStateInterface> SaveState() override { - T curr_value = Get(); - - absl::MutexLock l(InitFlagIfNecessary()); - - return absl::make_unique<flags_internal::FlagState<T>>( - this, std::move(curr_value), modified_, on_command_line_, counter_); + return impl_.SaveState(this); } // Restores the flag state to the supplied state object. If there is // nothing to restore returns false. Otherwise returns true. bool RestoreState(const flags_internal::FlagState<T>& flag_state) { - if (MutationCounter() == flag_state.counter_) return false; - - Set(flag_state.cur_value_); + return impl_.RestoreState(*this, &flag_state.cur_value_, + flag_state.modified_, flag_state.on_command_line_, + flag_state.counter_); + } - // Race condition here? This should disappear once we move the rest of the - // flag's data into Flag's internals. + bool SetFromString(absl::string_view value, + flags_internal::FlagSettingMode set_mode, + flags_internal::ValueSource source, + std::string* error) override { + return impl_.SetFromString(*this, value, set_mode, source, error); + } - absl::MutexLock l(InitFlagIfNecessary()); - modified_ = flag_state.modified_; - on_command_line_ = flag_state.on_command_line_; - return true; + void CheckDefaultValueParsingRoundtrip() const override { + impl_.CheckDefaultValueParsingRoundtrip(*this); } - // Interfaces to overate on callbacks. - void InvokeCallback() override - ABSL_EXCLUSIVE_LOCKS_REQUIRED(locks_->primary_mu) { - flags_internal::InvokeCallback(&locks_->primary_mu, &locks_->callback_mu, - callback_); + private: + friend class FlagState<T>; + + void Destroy() const override { impl_.Destroy(); } + + void Read(void* dst) const override { + impl_.Read(*this, dst, &flags_internal::FlagOps<T>); + } + flags_internal::FlagOpFn TypeId() const override { + return &flags_internal::FlagOps<T>; } // Flag's data - // For some types, a copy of the current value is kept in an atomically - // accessible field. - std::atomic<int64_t> atomic_; - FlagCallback callback_; // Mutation callback + FlagImpl impl_; }; template <typename T> diff --git a/absl/flags/internal/registry.cc b/absl/flags/internal/registry.cc index 6b2564d13a78..ae7671a967e8 100644 --- a/absl/flags/internal/registry.cc +++ b/absl/flags/internal/registry.cc @@ -118,7 +118,7 @@ void FlagRegistry::RegisterFlag(CommandLineFlag* flag) { (flag->IsRetired() ? old_flag->Filename() : flag->Filename()), "'."), true); - } else if (flag->op_ != old_flag->op_) { + } else if (flag->TypeId() != old_flag->TypeId()) { flags_internal::ReportUsageError( absl::StrCat("Flag '", flag->Name(), "' was defined more than once but with " @@ -275,38 +275,49 @@ namespace { class RetiredFlagObj final : public flags_internal::CommandLineFlag { public: - constexpr RetiredFlagObj(const char* name, FlagOpFn ops, - FlagMarshallingOpFn marshalling_ops) + constexpr RetiredFlagObj(const char* name, FlagOpFn ops) : flags_internal::CommandLineFlag( name, flags_internal::HelpText::FromStaticCString(nullptr), - /*filename=*/"RETIRED", ops, marshalling_ops, - /*initial_value_gen=*/nullptr, - /*def=*/nullptr, - /*cur=*/nullptr) {} + /*filename=*/"RETIRED"), + op_(ops) {} private: - bool IsRetired() const override { return true; } - void Destroy() const override { // Values are heap allocated for Retired Flags. - if (cur_) Delete(op_, cur_); - if (def_) Delete(op_, def_); - - if (locks_) delete locks_; - delete this; } + flags_internal::FlagOpFn TypeId() const override { return op_; } + bool IsRetired() const override { return true; } + bool IsModified() const override { return false; } + bool IsSpecifiedOnCommandLine() const override { return false; } + std::string DefaultValue() const override { return ""; } + std::string CurrentValue() const override { return ""; } + + // Any input is valid + bool ValidateInputValue(absl::string_view) const override { return true; } + std::unique_ptr<flags_internal::FlagStateInterface> SaveState() override { return nullptr; } + + bool SetFromString(absl::string_view, flags_internal::FlagSettingMode, + flags_internal::ValueSource, std::string*) override { + return false; + } + + void CheckDefaultValueParsingRoundtrip() const override {} + + void Read(void*) const override {} + + // Data members + const FlagOpFn op_; }; } // namespace -bool Retire(const char* name, FlagOpFn ops, - FlagMarshallingOpFn marshalling_ops) { - auto* flag = new flags_internal::RetiredFlagObj(name, ops, marshalling_ops); +bool Retire(const char* name, FlagOpFn ops) { + auto* flag = new flags_internal::RetiredFlagObj(name, ops); FlagRegistry::GlobalRegistry()->RegisterFlag(flag); return true; } diff --git a/absl/flags/internal/registry.h b/absl/flags/internal/registry.h index eb134a9f81e9..1889f3a076dd 100644 --- a/absl/flags/internal/registry.h +++ b/absl/flags/internal/registry.h @@ -76,14 +76,12 @@ bool RegisterCommandLineFlag(CommandLineFlag*); // // Retire flag with name "name" and type indicated by ops. -bool Retire(const char* name, FlagOpFn ops, - FlagMarshallingOpFn marshalling_ops); +bool Retire(const char* name, FlagOpFn ops); // Registered a retired flag with name 'flag_name' and type 'T'. template <typename T> inline bool RetiredFlag(const char* flag_name) { - return flags_internal::Retire(flag_name, flags_internal::FlagOps<T>, - flags_internal::FlagMarshallingOps<T>); + return flags_internal::Retire(flag_name, flags_internal::FlagOps<T>); } // If the flag is retired, returns true and indicates in |*type_is_bool| |