Flow 1.0.1
Flow project: Full implementation reference.
Classes | Public Types | Public Member Functions | Public Attributes | Static Public Attributes | Private Types | Private Member Functions | Private Attributes | Related Functions | List of all members
flow::cfg::Config_manager< S_d_value_set > Class Template Reference

Manages a config setup, intended for a single daemon process, by maintaining 1 or more set(s) of static config and dynamic config, each, via that number of Option_set<>-ready raw value struct types supplied by the user as template arguments. More...

#include <cfg_manager.hpp>

Inheritance diagram for flow::cfg::Config_manager< S_d_value_set >:
[legend]
Collaboration diagram for flow::cfg::Config_manager< S_d_value_set >:
[legend]

Classes

struct  On_dynamic_change_func_handle
 Opaque handle for managing a dynamic config change callback. More...
 

Public Types

enum  allow_invalid_defaults_tag_t { S_ALLOW_INVALID_DEFAULTS }
 Tag type: indicates an apply_*() method must allow invalid defaults and only complain if the config source does not explicitly supply a valid value. More...
 
using On_dynamic_change_func = Function< void()>
 Short-hand for a callback to execute on dynamic config change. More...
 

Public Member Functions

 Config_manager (log::Logger *logger_ptr, util::String_view nickname, typename Option_set< S_d_value_set >::Declare_options_func &&... declare_opts_func_moved)
 Constructs a Config_manager ready to read initial config via apply_*() and other setup methods; and further capable of both static and dynamic config. More...
 
template<typename... Value_set>
bool apply_static (const fs::path &static_cfg_path, const typename Final_validator_func< Value_set >::Type &... final_validator_func, bool commit=true)
 Invoke this after construction to load the permanent set of static config from config sources including a static config file; if you are also using dynamic config, see apply_static_and_dynamic() as a potential alternative. More...
 
template<typename... Value_set>
bool apply_static (allow_invalid_defaults_tag_t, const fs::path &static_cfg_path, const typename Final_validator_func< Value_set >::Type &... final_validator_func, bool commit=true)
 Identical to apply_static() overload without allow_invalid_defaults_tag_t tag; but skips the stringent check on individual defaults' validity. More...
 
bool apply_static_and_dynamic (const fs::path &cfg_path, const typename Final_validator_func< S_d_value_set >::Type &... final_validator_func, bool commit=true)
 If you use dynamic config, and you allow for initial values for dynamic options to be read from the same file as the static config values, then invoke this instead of apply_static(). More...
 
bool apply_static_and_dynamic (allow_invalid_defaults_tag_t, const fs::path &cfg_path, const typename Final_validator_func< S_d_value_set >::Type &... final_validator_func, bool commit=true)
 Identical to apply_static_and_dynamic() overload without allow_invalid_defaults_tag_t tag; but skips the stringent check on individual defaults' validity. More...
 
template<typename... Value_set>
bool apply_dynamic (const fs::path &dynamic_cfg_path, const typename Final_validator_func< Value_set >::Type &... final_validator_func, bool commit=true)
 Load the first or subsequent set of dynamic config from config source including a dynamic config file. More...
 
template<typename... Value_set>
bool apply_dynamic (allow_invalid_defaults_tag_t, const fs::path &dynamic_cfg_path, const typename Final_validator_func< Value_set >::Type &... final_validator_func, bool commit=true)
 Identical to apply_dynamic() overload without allow_invalid_defaults_tag_t tag; except that – applicably only to the initial apply_dynamic() without a preceding apply_static_and_dynamic() – skips the stringent check on individual defaults' validity. More...
 
void reject_candidates ()
 Cancel a not-yet-canonicalized (incomplete) multi-source update, if one is in progress. More...
 
template<typename... Value_set>
void all_static_values (const Value_set **... value_set_or_null) const
 Emit a pointer to each permanently set static config value set; the same pointers are emitted throughout for each of the S_N_S_VALUE_SETS static config slots. More...
 
template<typename... Value_set>
void all_static_values_candidates (const Value_set **... value_set_or_null) const
 Similar to all_static_values(), but if called from within a validator function passed to apply_static() or apply_static_and_dynamic(), then for any static value set for which values have been parsed and validated (but not yet applied) so far, a pointer to the parsed values candidate will be emitted instead. More...
 
template<typename Value_set >
const Value_set & static_values (size_t s_value_set_idx) const
 Similar to all_static_values(), but obtains the static config in one specified slot as opposed to all of them. More...
 
template<typename Value_set >
const Value_set & static_values_candidate (size_t s_value_set_idx) const
 Similar to static_values(), but if called from within a validator function passed to apply_static() or apply_static_and_dynamic(), then the parsed values candidate will be returned, instead, if values have been parsed and validated for the static value set but have not yet been applied. More...
 
template<typename... Value_set>
void all_dynamic_values (typename Value_set::Const_ptr *... value_set_or_null) const
 Obtain ref-counted pointers to each currently-canonical set of dynamic config; each pointed-at struct is set permanently; while another call may return a different pointer if config is changed dynamically in the meantime (for that slot). More...
 
template<typename Value_set >
Value_set::Const_ptr dynamic_values (size_t d_value_set_idx) const
 Similar to all_dynamic_values() but obtains the dynamic config in one specified slot as opposed to all of them. More...
 
template<typename Value_set >
On_dynamic_change_func_handle register_dynamic_change_listener (size_t d_value_set_idx, On_dynamic_change_func &&on_dynamic_change_func_moved)
 Saves the given callback; next time apply_dynamic(commit = true) or apply_static_and_dynamic(commit = true) detects at least one changed (or initially set) option value in the specified slot, it will execute this and any other previously registered such callbacks synchronously. More...
 
void unregister_dynamic_change_listener (const On_dynamic_change_func_handle &handle)
 Remove a previously registered dynamic change callback. More...
 
void state_to_ostream (std::ostream &os) const
 Prints a human-targeted long-form summary of our contents, doubling as a usage message and a dump of current values where applicable. More...
 
void log_state (log::Sev sev=log::Sev::S_INFO) const
 Logs what state_to_ostream() would print. More...
 
void help_to_ostream (std::ostream &os) const
 Prints a human-targeted long-form usage message that includes all options with their descriptions and defaults. More...
 
void log_help (log::Sev sev=log::Sev::S_INFO) const
 Logs what help_to_ostream() would print. More...
 
- Public Member Functions inherited from flow::log::Log_context
 Log_context (Logger *logger=0)
 Constructs Log_context by storing the given pointer to a Logger and a null Component. More...
 
template<typename Component_payload >
 Log_context (Logger *logger, Component_payload component_payload)
 Constructs Log_context by storing the given pointer to a Logger and a new Component storing the specified generically typed payload (an enum value). More...
 
 Log_context (const Log_context &src)
 Copy constructor that stores equal Logger* and Component values as the source. More...
 
 Log_context (Log_context &&src)
 Move constructor that makes this equal to src, while the latter becomes as-if default-constructed. More...
 
Log_contextoperator= (const Log_context &src)
 Assignment operator that behaves similarly to the copy constructor. More...
 
Log_contextoperator= (Log_context &&src)
 Move assignment operator that behaves similarly to the move constructor. More...
 
void swap (Log_context &other)
 Swaps Logger pointers and Component objects held by *this and other. More...
 
Loggerget_logger () const
 Returns the stored Logger pointer, particularly as many FLOW_LOG_*() macros expect. More...
 
const Componentget_log_component () const
 Returns reference to the stored Component object, particularly as many FLOW_LOG_*() macros expect. More...
 

Public Attributes

const std::string m_nickname
 See nickname ctor arg. More...
 

Static Public Attributes

static constexpr size_t S_N_VALUE_SETS = sizeof...(S_d_value_set)
 The number of template params in this Config_manager instantiation. It must be even and positive. More...
 
static constexpr size_t S_N_S_VALUE_SETS = sizeof...(S_d_value_set) / 2
 The number of static value sets (including any Null_value_sets). More...
 
static constexpr size_t S_N_D_VALUE_SETS = sizeof...(S_d_value_set) / 2
 The number of dynamic value sets (including any Null_value_sets). More...
 

Private Types

enum class  Update_type { S_NONE , S_STATIC , S_STATIC_AND_DYNAMIC , S_DYNAMIC }
 Useful at least in the context of multi-source (commit == false) apply_*() methods, this distinguishes distinct possible public apply_*() methods. More...
 
using Void_ptr = boost::shared_ptr< void >
 Short-hand for shared_ptr-to-void type used to store variable-type values in internal containers. More...
 

Private Member Functions

template<typename Value_set >
Option_set< Value_set > * opt_set (size_t value_set_idx)
 Helper that obtains the Option_set in the slot m_s_d_opt_sets[value_set_idx], which stores an Option_set<Value_set>. More...
 
template<typename Value_set >
Value_set * d_baseline_value_set (size_t d_value_set_idx)
 Helper that obtains the baseline dynamic Value_set in the slot m_d_baseline_value_sets[value_set_idx], which stores a Value_set. More...
 
template<typename Value_set >
const Option_set< Value_set > * opt_set (size_t value_set_idx) const
 const overload of the other opt_set() helper. More...
 
template<typename Value_set >
void option_set_canonicalize_or_reject (Option_set< Value_set > *opt_set, bool canonicalize_else_reject, bool *changed_or_null)
 Helper that executes opt_set->canonicalize_candidate() or opt_set->reject_candidate(). More...
 
template<typename... Value_set>
bool apply_static_impl (bool allow_invalid_defaults, const fs::path &static_cfg_path, const typename Final_validator_func< Value_set >::Type &... final_validator_func, bool commit)
 Implements all apply_static() overloads. More...
 
template<typename... Value_set>
bool apply_dynamic_impl (bool allow_invalid_defaults, const fs::path &dynamic_cfg_path, const typename Final_validator_func< Value_set >::Type &... final_validator_func, bool commit)
 Implements all apply_dynamic() overloads. More...
 
bool apply_static_and_dynamic_impl (bool allow_invalid_defaults, const fs::path &cfg_path, const typename Final_validator_func< S_d_value_set >::Type &... final_validator_func, bool commit)
 Implements all apply_static_and_dynamic() overloads. More...
 
template<typename... Value_set>
bool apply_static_or_dynamic_impl (bool dyn_else_st, const fs::path &cfg_path, const boost::unordered_set< std::string > &all_opt_names_or_empty, const typename Final_validator_func< Value_set >::Type &... final_validator_func)
 Work-horse helper that parses either all static value sets or all dynamic value sets from the specified file into m_s_d_opt_sets and returns true if and only if all have succeeded. More...
 
template<typename Value_set >
bool apply_impl (Option_set< Value_set > *opt_set, const Value_set *baseline_value_set_or_null, const fs::path &cfg_path, const boost::unordered_set< std::string > &all_opt_names_or_empty, const typename Final_validator_func< Value_set >::Type &final_validator_func, bool *skip_parsing)
 Work-horse helper that parses into one given Option_set in m_s_d_opt_sets (from opt_set()) from the specified file and returns true if and only if successful. More...
 
void reject_candidates_if_update_type_changed (Update_type this_update_type)
 Helper for the top of apply_*() that guards against a call to apply_Y() following apply_X(commit == false) == true without a reject_candidates() between them. More...
 
template<typename Value_set >
void save_dynamic_value_set_locked (Option_set< Value_set > *opt_set, size_t d_value_set_idx)
 Little helper that, having assumed m_d_value_sets_mutex is locked, makes a newly allocated copy of the canonical dynamic config at the given slot and saves a (new) ref-counted pointer to it in the proper m_d_value_sets slot. More...
 
void invoke_dynamic_change_listeners (size_t d_value_set_idx, bool init) const
 Invokes the registered listeners for the given dynamic config slot (synchronously). More...
 
template<typename Value_set >
void state_to_ostream_impl (size_t value_set_idx, std::ostream &os) const
 Helper of state_to_ostream() for m_s_d_opt_sets slot value_set_idx. More...
 
template<typename Value_set >
void help_to_ostream_impl (size_t value_set_idx, std::ostream &os) const
 Helper of help_to_ostream() for m_s_d_opt_sets slot value_set_idx. More...
 

Private Attributes

boost::array< Void_ptr, S_N_VALUE_SETSm_s_d_opt_sets
 The static and dynamic value sets, in the same order as the S_N_VALUE_SETS S_d_value_set template args, the specific type for each slot being: Option_set<S_d_value_set>. More...
 
boost::array< Void_ptr, S_N_D_VALUE_SETSm_d_baseline_value_sets
 The baseline dynamic value sets, in the same order as S_N_D_VALUE_SETS dynamic S_d_value_set template args, the specific type for each slot being S_d_value_set. More...
 
boost::array< Void_ptr, S_N_D_VALUE_SETSm_d_value_sets
 The dynamic config ref-counted handles returned by all_dynamic_values(). More...
 
util::Mutex_non_recursive m_d_value_sets_mutex
 Mutex protecting m_d_value_sets. More...
 
boost::array< std::list< On_dynamic_change_func >, S_N_D_VALUE_SETSm_on_dynamic_change_funcs
 List of callbacks to execute after m_d_value_sets members (the pointers) are next assigned: one per each of the S_N_D_VALUE_SETS dynamic config slots. More...
 
bool m_dynamic_values_set
 Starts false; set to true permanently on successful apply_static_and_dynamic() or apply_dynamic(). More...
 
Update_type m_multi_src_update_in_progress
 In short, truthy if and only if a commit == false update is currently in progress, meaning there's an uncommitted candidate Value_set at the moment. More...
 

Related Functions

(Note that these are not member functions.)

template<typename... S_d_value_set>
std::ostream & operator<< (std::ostream &os, const Config_manager< S_d_value_set... > &val)
 Serializes (briefly) a Config_manager to a standard output stream. More...
 

Detailed Description

template<typename... S_d_value_set>
class flow::cfg::Config_manager< S_d_value_set >

Manages a config setup, intended for a single daemon process, by maintaining 1 or more set(s) of static config and dynamic config, each, via that number of Option_set<>-ready raw value struct types supplied by the user as template arguments.

There is an even number of template args (or it will not compile); each successive pair contains, in order, a static Value_set followed by a dynamic one. It is possible, for each given pair, to use static config alone or dynamic config alone; in that case pass in Null_value_set for the unused one:

// First config pair is static-only; second config pair -- for a separate application module perhaps --
// has both a static and dynamic parts.
Manages a config setup, intended for a single daemon process, by maintaining 1 or more set(s) of stat...
See also
Static_config_manager for the probably-common use case when you have only static config and only one struct at that. Static_config_manager adapts Config_manager with a very simple API, avoiding parameter packs and any mention of various dynamic-config complexities.
Todo:
Dynamic config support lacks crash rejection; though these features should be an incremental improvement around the existing code. By crash rejection I mean something like: config file X comes in; we rename it to X.unparsed; we try to parse it; program crashes – versus it's fine, so we rename it X.parsed, and next time X.parsed is what we presumably-safely parse after any restart. Similarly invalid config may not cause a crash but still shouldn't be repeatedly re-parsed (etc.). Exact design TBD and will be informed by previous work on other projects (by echan and ygoldfel at least).

When (and how) to use this

First see the namespace flow::cfg doc header for a brief overview. If you have chosen to use flow::cfg for (at least some of) your config needs, what is mandatory to use is the class template Option_set. The choice is to use it directly (writing your own facilities around it) or to use this Config_manager to maintain a couple of Option_sets for you in a straightforward way. Config_manager supports that one straightforward way, and for daemon programs it may be a good choice. Otherwise, the idea behind Option_set is to be able to flexibly use them as needed (Config_manager providing one such way).

Firstly, you may create a const (immutable) Config_manager via its constructor and then just use it to output a help message (log_help() or help_to_ostream()). This could be used with your program's --help option or similar, and that's it (no parsing takes place).

// Example of a static-config-only Config_manager created just to log the help, and that's it.
(get_logger(), "cfg", &static_cfg_declare_opts, flow::cfg::null_declare_opts_func())
void log_help(log::Sev sev=log::Sev::S_INFO) const
Logs what help_to_ostream() would print.
Logger * get_logger() const
Returns the stored Logger pointer, particularly as many FLOW_LOG_*() macros expect.
Definition: log.cpp:224
Option_set< Null_value_set >::Declare_options_func null_declare_opts_func()
Returns a value usable as declare_opts_func_moved Config_manager ctor arg for a Null_value_set value ...
Definition: cfg_manager.cpp:26

Orthogonally, of course, you may want to use it to parse things. In which case:

Config_manager assumes the following setup:

To summarize: This is the assumed order of API calls on a given *this; other orders may lead to undefined behavior:

In addition, between Config_manager ctor and dtor:

Advanced feature: multi-source parsing and source skipping

By default this feature is not in play, as the bool commit arg to apply_static_and_dynamic(), apply_static(), and apply_dynamic() defaults to true. The multi-source feature is enabled by the user setting it to false for some calls to apply_*(). To explain how it works consider one particular Value_set and an attempt to execute one of apply_*() methods w/r/t that Value_set (among potentially others to which the same discussion applies equally). For purposes of discussion assume it is a dynamic Value_set, and the operation in question is apply_dynamic(); the same points apply to static ones and the other two apply_*() calls except where noted below.

Suppose the canonical (as returned by dynamic_values() or all_dynamic_values()) Value_set is in state B, the baseline state. (If Value_set is static, then B is the default state from default-cted Value_set, and the canonical-state accessors are static_values() and all_static_values().) Normally a single update consists of just one call to apply_dynamic(commit = true):

  1. R = apply_dynamic(P, F, true), where P is a file path, and F is your Final_validator_func::Type function.

If F() == Final_validator_outcome::S_FAIL, then R == false; state stays at B. If it's F() == S_ACCEPT (and the individual options were all fine), then R == true; state becomes B'; and if B does not equal B' (a change was detected), then the dynamic changes listener(s) (from register_dynamic_change_listener()) are synchronously called before apply_dynamic() returns. (That listener stuff does not apply to static Value_sets in apply_static() and apply_static_and_dynamic().)

That's the default behavior without engaging this feature. To engage the feature, one has to call apply_dynamic(commit = false) at least once. A single update now consists of 2+ calls to apply_dynamic():

  1. R = apply_dynamic(P1, F, false); if !R, update failed/exit algorithm; else:
  2. R = apply_dynamic(P2, F, false); if !R, update failed/exit algorithm; else:
  3. ...
  4. R = apply_dynamic(Pn, F, true); if !R, update failed/exit algorithm; else done. The true arg indicates the final call.

(F can be different each time, though informally we suspect that would be unorthodox. It is allowed formally.)

Each apply_dynamic() call builds up a candidate Value_set C which is created (at the top of call 1) to equal baseline/initial state B and then potentially incrementally morphed by each apply_dynamic() call. Assuming no error (from individual validator or an F() == S_FAIL) at any stage, the effect on the candidate Value_set C is as follows:

  1. If F(Cnext) == S_ACCEPT: Modify the candidate by overwriting it: C = Cnext;.
  2. If F(Cnext) == S_SKIP: Keep the candidate Value_set C unchanged from its current value at entry to that apply_dynamic() call. Cnext is discarded (so the incremental update to the candidate is skipped).

However, and this is the key:

(Hence the one-call update scenario (the vanilla one) is merely a degenerate case of the multi-call scenario; as then the last call is the only call and thus canonicalizes the candidate and calls dynamic change listeners if appropriate.)

If at any stage F(Cnext) yields Final_validator_outcome::S_FAIL, then apply_dynamic() returns false. This means the entire update has failed; the candidate is abandoned, and you should not make the subsequent apply_dynamic() calls that may be remaining in the update. The next such call will be taken to be the 1st of another update, with a fresh candidate C created to equal B.

Why use this? Why use 2+ files for a given update? Indeed you shouldn't, unless there is specifically a good reason. What might that be? The precipitating use case, and the basic one immediately obvious to the authors, is where exactly for 1 of the files the final-validator shall return Final_validator_outcome::S_ACCEPT, while for all others it will return Final_validator_outcome::S_SKIP. For example, if your distributed network consists of 3 sections, and each machine knows to which section it belongs, and each file is aimed at exactly 1 of the 3 and thus specifies it as some option if-section-is= (possible values 1 or 2 or 3), then the final-validator func would:

  1. Check if-section-is option and return SKIP if the machine's actual section does not match it. Else:
  2. Ensure internal consistency of candidate Value_set (as usual).
  3. Return FAIL or ACCEPT depending on the answer to that (as usual).

Now you've enabled conditional config in a distributed deployment.

Warning
In this conditional config use case, it is likely best to declare any given conditional option using a _NO_ACC variation of your FLOW_CFG_OPTION_SET_DECLARE_OPTION*() option of choice; that is FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC() or similarly-postfixed variation. You probably do not want such an option's value to persist from an earlier file in the update to a later one; instead it should probably reset to its default "at the top" of each file; _NO_ACC variants will accomplish this.

That said the use of SKIP (for all but 1) is not mandatory in such a multi-file-update setup. You can never-SKIP, or you can ACCEPT (not-SKIP) 2+ files; it is conceivable those setups have use cases as well. Informally though we reason to you as follows:

The above are informal recommendations for maximum simplicity or clarity, but it is entirely conceivable a more complex situation arises and requires they not be heeded.

Relationship with the rest of your program/threads

At this time Config_manager is deliberately simple in that it doesn't start any threads of its own; it doesn't watch file modifications to detect dynamic changes; and its methods can be called from any thread and always act entirely synchronously. (The only methods affected by thread considerations are apply_dynamic() and dynamic_values() + all_dynamic_values(); they are intentionally thread-safe w/r/t each other.)

To the extent that it uses callbacks – again, only for dynamic config needs and only optionally – they, too, are executed synchronously. However, these callbacks can (of course) .post() work onto boost.asio util::Task_engine or async::Concurrent_task_loop or async::Single_thread_task_loop.

Informally: It is, however, possible and perhaps probable that one should build more facilities to detect cfg changes of various kinds; for this thread(s) may be useful. We suspect it is better to build class(es) that do that and make use of Config_manager – not expand Config_manager to do such things. Its simplicity and relative light weight and flexibility are good properties to conserve and not saddle with baggage (even optional baggage).

Thread safety

This is discussed in bits and pieces above and in various doc headers. Here is the overall picture however:

Template Parameters
S_d_value_setAn even number (at least 2) of settings structs – see Option_set doc header for requirements for each – in order static, dynamic, static, dynamic, .... Use Null_value_set as needed; but do not use Null_value_set for both the static and dynamic parameter in a given pair (e.g., <Null_value_set, Null_value_set, ...> is both useless and formally disallowed).

Implementation notes

I (ygoldfel) wrote this, originally, in 2 stages. At first it only supported exactly 2 template args: one static and one dynamic Value_set. In the next stage I turned it into a parameter pack, so that 4, 6, 8, ... args can be provided if desired – to support multiple configurable modules in the application. Before this I had done some basic parameter-pack coding, but undoubtedly Config_manager is my first foray into this level of sophistication. Certainly I welcome changes to improve clarity of the implementation and other suggestions. Generally speaking the code does strike me as complex, though there's a good case to be made to the effect of: That is just the nature of the beast. It's meta-programming: having the compiler perform loops, of a sort, at compile time. Because S_d_value_set... is not homogenous – within each adjacent pair of tparams the first and the second are not always treated equivalently – the code has to be tricky at times.

That said the usability of the API should not be affected by these details much. At times one has to supply additional <template args>apply_static() and apply_dynamic() come to mind in particular – to help the compiler; but the compiler will complain about being unable to deduce the tparams, and then the next action for the user is straightforward.

A particular trick explained

In C++17 fold expressions are available (https://en.cppreference.com/w/cpp/language/fold), and we use them to create compile-time loops without the appalling coding/maintenance overhead of the meta-recursive pattern. (This replaces a much uglier trick involving a dummy int[] initializer, though that was still superior to doing the meta-recursive thing all over the place.)

Suppose, say, you want to call f<Value_set>(idx, dyn_else_st), where idx runs through [0, S_N_VALUE_SETS), while dyn_else_st alternates between false (static) and true (dynamic). E.g., f() might parse the idxth slot of m_s_d_opt_sets and do something extra if it's an odd (dynamic) slot. Note f() is a template parameterized on Value_set. Then:

size_t idx = 0;
bool dyn_else_st = false;
(
..., // This indicates the entire (..., expr) is a fold-expression, where...
(
// ...the `S_d_value_set` in the following expression is the kernel of the param-pack expansion;
// due to the fact the class template is: template<typename... S_d_value_set> class Config_manager
f<S_d_value_set>(idx, dyn_else_st),
++idx,
dyn_else_st = !dyn_else_st)
)
);

(..., expr) expands to, ultimately, (expr1, expr2, ..., expr3), where each expr...i... is expr with the i-th S_d_value_set. In our case expr...i... is itself a comma-expression with 3 sub-expressions. Comma-expression evaluates left-to-right, so at runtime the side effects of each expr...i... are occurring at runtime; in this case this includes incrementing idx and repeatedly flipping dyn_else_st.

In the actual code, often we don't (just) call some helper f<>() but rather actually perform some steps in-line there instead – similarly to how this example does a couple of simple expressions after calling f<>(). The good thing is the reader need not jump to another function (and all that maintenance overhead). The bad thing is the language constructions within an expression in C++ (not a functional language) are limited. Loops aren't possible; if ()s have to be replaced by ?: ternaries or && or || expressions; and so on. It's not always the most readable thing, if one isn't used to it, but the author feels it's doable to get used to it, and it's worth it for the brevity.

Definition at line 383 of file cfg_manager.hpp.

Member Typedef Documentation

◆ On_dynamic_change_func

template<typename... S_d_value_set>
using flow::cfg::Config_manager< S_d_value_set >::On_dynamic_change_func = Function<void ()>

Short-hand for a callback to execute on dynamic config change.

Definition at line 401 of file cfg_manager.hpp.

◆ Void_ptr

template<typename... S_d_value_set>
using flow::cfg::Config_manager< S_d_value_set >::Void_ptr = boost::shared_ptr<void>
private

Short-hand for shared_ptr-to-void type used to store variable-type values in internal containers.

Definition at line 994 of file cfg_manager.hpp.

Member Enumeration Documentation

◆ allow_invalid_defaults_tag_t

template<typename... S_d_value_set>
enum flow::cfg::Config_manager::allow_invalid_defaults_tag_t

Tag type: indicates an apply_*() method must allow invalid defaults and only complain if the config source does not explicitly supply a valid value.

Otherwise the defaults themselves are also stringently checked regardless of whether they are overridden. This setting applies only to individual-option-validators. Final_validator_func validation is orthogonal to this.

Enumerator
S_ALLOW_INVALID_DEFAULTS 

Sole value for tag type allow_invalid_defaults_tag_t.

Definition at line 395 of file cfg_manager.hpp.

◆ Update_type

template<typename... S_d_value_set>
enum class flow::cfg::Config_manager::Update_type
strongprivate

Useful at least in the context of multi-source (commit == false) apply_*() methods, this distinguishes distinct possible public apply_*() methods.

Enumerator
S_NONE 

Indicates no apply_*() operation.

S_STATIC 

Indicates apply_static().

S_STATIC_AND_DYNAMIC 

Indicates apply_static_and_dynamic().

S_DYNAMIC 

Indicates apply_dynamic().

Definition at line 1000 of file cfg_manager.hpp.

Constructor & Destructor Documentation

◆ Config_manager()

template<typename... S_d_value_set>
flow::cfg::Config_manager< S_d_value_set >::Config_manager ( log::Logger logger_ptr,
util::String_view  nickname,
typename Option_set< S_d_value_set >::Declare_options_func &&...  declare_opts_func_moved 
)
explicit

Constructs a Config_manager ready to read initial config via apply_*() and other setup methods; and further capable of both static and dynamic config.

See class doc header for class life cycle instructions.

Logging assumption

*logger_ptr is a standard logging arg. Note, though, that the class will assume that log verbosity may not have yet been configured – since this Config_manager may be the thing configuring it. Informal recommendations:

  • You should let through INFO and WARNING messages in *logger_ptr.
  • If you plan to use *this only for log_help() (such as in your --help implementation), you should not let through TRACE-or-more-verbose.
  • Once (and if) you engage any actual parsing (apply_static(), etc.), TRACE may be helpful in debugging as usual.
Parameters
logger_ptrLogger to use for subsequently logging.
nicknameBrief string used for logging subsequently.
declare_opts_func_movedFor each S_d_value_set, in order, the declare-options callback as required by Option_set<S_d_value_set> constructor; see its doc header for instructions. For each Null_value_set: use the function returned by null_declare_opts_func().

Definition at line 1470 of file cfg_manager.hpp.

References FLOW_LOG_TRACE, flow::log::Log_context::get_logger(), flow::cfg::Config_manager< S_d_value_set >::m_d_baseline_value_sets, flow::cfg::Config_manager< S_d_value_set >::m_d_value_sets, flow::cfg::Config_manager< S_d_value_set >::m_nickname, flow::cfg::Config_manager< S_d_value_set >::m_s_d_opt_sets, flow::util::ostream_op_string(), flow::cfg::Config_manager< S_d_value_set >::S_N_S_VALUE_SETS, and flow::cfg::Config_manager< S_d_value_set >::S_N_VALUE_SETS.

Here is the call graph for this function:

Member Function Documentation

◆ all_dynamic_values()

template<typename... S_d_value_set>
template<typename... Value_set>
void flow::cfg::Config_manager< S_d_value_set >::all_dynamic_values ( typename Value_set::Const_ptr *...  value_set_or_null) const

Obtain ref-counted pointers to each currently-canonical set of dynamic config; each pointed-at struct is set permanently; while another call may return a different pointer if config is changed dynamically in the meantime (for that slot).

For each slot: If you require a consistent set of dynamic config over some period of time or over some code path, save a copy of the returned ref-counted pointer to the Value_set and keep accessing values in that immutable structure through that ref-counted pointer.

After the initial apply_dynamic(): It is safe to call all_dynamic_values() concurrently with any number of itself or of dynamic_values() and/or a call to apply_dynamic(commit = *).

Note
Have each Value_set derive from util::Shared_ptr_alias_holder, so that it is endowed with Ptr and Const_ptr ref-counted pointer type aliases, for your convenience and as required by Config_manager and Option_set.

Performance

dynamic_values() performance is no worse than: lock mutex, copy S_N_D_VALUE_SETS shared_ptrs, unlock mutex. See also Performance section of apply_dynamic() doc header.

Template Parameters
Value_setThese must be S_d_value_set template param pack args 1, 3, ..., numbering S_N_D_VALUE_SETS in order. Likely you will need to explicitly specify them, as the compiler will probably not deduce them. E.g.: cfg_mgr.all_dynamic_values<D_cfg1, D_cfg2, Null_value_set>(&d1, &d2, 0).
Parameters
value_set_or_nullFor each element in the param pack: *value_set_or_null is set to point to the immutable dynamic config set currently canonical in that slot; or that slot is ignored if value_set_or_null is null. Remember: **value_set_or_null is immutable; but another call may yield a different *value_set_or_null.

Definition at line 2539 of file cfg_manager.hpp.

◆ all_static_values()

template<typename... S_d_value_set>
template<typename... Value_set>
void flow::cfg::Config_manager< S_d_value_set >::all_static_values ( const Value_set **...  value_set_or_null) const

Emit a pointer to each permanently set static config value set; the same pointers are emitted throughout for each of the S_N_S_VALUE_SETS static config slots.

Tip: It should be sufficient to pass around only const refs (from the pointers obtained here) all around the app – no Value_set copying should be needed.

Template Parameters
Value_setThese must be S_d_value_set template param pack args 0, 2, ..., numbering S_N_S_VALUE_SETS in order. The compiler should be able to deduce them automatically from each value_set_or_null type; though when passing in null it may be then necessary to cast nullptr (or 0) to the appropriate pointer type.
Parameters
value_set_or_nullFor each element in the param pack: *value_set_or_null is set to point to the immutable static config set in that slot; or that slot is ignored if value_set_or_null is null.

Definition at line 2455 of file cfg_manager.hpp.

◆ all_static_values_candidates()

template<typename... S_d_value_set>
template<typename... Value_set>
void flow::cfg::Config_manager< S_d_value_set >::all_static_values_candidates ( const Value_set **...  value_set_or_null) const

Similar to all_static_values(), but if called from within a validator function passed to apply_static() or apply_static_and_dynamic(), then for any static value set for which values have been parsed and validated (but not yet applied) so far, a pointer to the parsed values candidate will be emitted instead.

If called from elsewhere, the behavior is equivalent to all_static_values(). A values candidate consists of what has been parsed and validated (and not skipped via Final_validator_outcome::S_SKIP) for the value set, but has not yet been applied such that it would be available through all_static_values().

This could be useful to provide static values to a final validator function for another value set which depends on them. During the execution of apply_static() or apply_static_and_dynamic(), the validator function for each value set will be executed before any are canonicalized. This method can be used by a validator function to obtain the candidate values which have been parsed and validated (but have not yet been canonicalized) for a preceding static value set.

See also
all_static_values().
Template Parameters
Value_setSee all_static_values().
Parameters
value_set_or_nullSee all_static_values().

Definition at line 2479 of file cfg_manager.hpp.

◆ apply_dynamic() [1/2]

template<typename... S_d_value_set>
template<typename... Value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_dynamic ( allow_invalid_defaults_tag_t  ,
const fs::path &  dynamic_cfg_path,
const typename Final_validator_func< Value_set >::Type &...  final_validator_func,
bool  commit = true 
)

Identical to apply_dynamic() overload without allow_invalid_defaults_tag_t tag; except that – applicably only to the initial apply_dynamic() without a preceding apply_static_and_dynamic() – skips the stringent check on individual defaults' validity.

See also
allow_invalid_defaults_tag_t doc header and/or return-value doc just below.
Template Parameters
Value_setSee other apply_dynamic().
Parameters
dynamic_cfg_pathSee other apply_dynamic().
final_validator_funcSee other apply_dynamic().
commitSee other apply_dynamic().
Returns
See other apply_dynamic(). However – assuming apply_static_and_dynamic() was not used, and this is the initial apply_dynamic() – the other apply_dynamic() will return false if a default is invalid, even if file dynamic_cfg_path explicitly sets it to a valid value. This tagged overload will not and let parsing continue. If apply_static_and_dynamic(commit = true) == true was used, or if apply_dynamic(commit = true) == true has been called before, then the overloads behave identically: defaults are not checked; it must occur just before the initial dynamic load or never.

Definition at line 2165 of file cfg_manager.hpp.

◆ apply_dynamic() [2/2]

template<typename... S_d_value_set>
template<typename... Value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_dynamic ( const fs::path &  dynamic_cfg_path,
const typename Final_validator_func< Value_set >::Type &...  final_validator_func,
bool  commit = true 
)

Load the first or subsequent set of dynamic config from config source including a dynamic config file.

If you use dynamic config: You must invoke this, or apply_static_and_dynamic(), once before access via dynamic_values() / all_dynamic_values(). After that, optionally invoke it whenever there is good reason to believe new dynamic config is available in the dynamic config source(s).

See also apply_static_and_dynamic(). If not using apply_static_and_dynamic(), and this is the initial apply_dynamic() invoked, then see also apply_dynamic() overload with allow_invalid_defaults_tag_t tag.

After the initial apply_dynamic(): It is safe to call apply_dynamic() concurrently with any number of [all_]dynamic_values(). However behavior is undefined if one calls apply_dynamic() concurrently with itself or apply_static_and_dynamic() (on the same *this).

On failure returns false; else returns true. In the former case the canonical state remains unchanged, and any candidate built-up via preceding commit == false calls (if any) is discarded. WARNING(s) logged given failure.

apply_dynamic() will be tolerant of unknown option names appearing in the config source, though it will log about them. The reasoning: Dynamic config must grapple with backward- and forward-compatibility.

For context w/r/t commit arg: Please read the section of the class doc header regarding multi-source updates. Corner case note: Formally: invoking apply_Y() immediately after apply_X(commit = false) == true, without reject_candidates() between them, where Y and X differ => apply_Y() shall log an INFO message and invoke reject_candidates() itself; then proceed. In this case apply_Y() is apply_dynamic(). Informally we discourage doing this; it is stylistically better to invoke reject_candidates() explicitly in that scenario which cancels an in-progress update which is unusual though conceivably useful.

Effect of a validator yielding SKIP

Identical to apply_static().

Performance

dynamic_values() + all_dynamic_values() locking performance is no worse than: lock mutex, assign S_N_D_VALUE_SETS shared_ptrs, unlock mutex. The rest of the parse/validate/etc. code is outside any such critical section. See also Performance section of dynamic_values() doc header.

Outside that locking critical section, apply_dynamic() may be expensive, in that it performs file input and some internal copying of option value sets and maps, plus detailed logging. It is, however, not typically called frequently. Just be aware it will block the calling thread, albeit still only for a split second in normal conditions.

Template Parameters
Value_setThese must be S_d_value_set template param pack args 1, 3, ..., numbering S_N_D_VALUE_SETS in order.
Parameters
dynamic_cfg_pathFile to read.
final_validator_funcSee apply_static_and_dynamic().
committrue means that this call being successful (returning true) shall cause the promotion of each candidate Value_set built-up so far (via this and all preceding successful calls with commit == false) to canonical state (accessed via dynamic_values() or all_dynamic_values()); and all relevant dynamic change listeners are synchronously called for each Value_set for which the cumulative candidate differs from the canonical state. false means that this call being successful shall merely create-and-set (if first such call) or incrementally update (if 2nd, 3rd, ... such call) each candidate Value_set; no dynamic change listeners are invoked.
Returns
true if and only if successfully parsed config source(s) and validated all settings including final_validator_func() != S_FAIL for all (dynamic) config sets; and defaults were also all individually valid, in the case of initial apply_dynamic(). However, if true but commit == false, then the canonical values (accessed via dynamic_values() or all_dynamic_values()) have not been updated.

Definition at line 2155 of file cfg_manager.hpp.

◆ apply_dynamic_impl()

template<typename... S_d_value_set>
template<typename... Value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_dynamic_impl ( bool  allow_invalid_defaults,
const fs::path &  dynamic_cfg_path,
const typename Final_validator_func< Value_set >::Type &...  final_validator_func,
bool  commit 
)
private

Implements all apply_dynamic() overloads.

Template Parameters
Value_setSee apply_dynamic().
Parameters
allow_invalid_defaultstrue if and only if allow_invalid_defaults_tag_t used.
dynamic_cfg_pathSee apply_dynamic().
final_validator_funcSee apply_dynamic().
commitSee apply_static().
Returns
See apply_dynamic().

Definition at line 2176 of file cfg_manager.hpp.

References FLOW_LOG_INFO, and FLOW_LOG_WARNING.

◆ apply_impl()

template<typename... S_d_value_set>
template<typename Value_set >
bool flow::cfg::Config_manager< S_d_value_set >::apply_impl ( Option_set< Value_set > *  opt_set,
const Value_set *  baseline_value_set_or_null,
const fs::path &  cfg_path,
const boost::unordered_set< std::string > &  all_opt_names_or_empty,
const typename Final_validator_func< Value_set >::Type &  final_validator_func,
bool skip_parsing 
)
private

Work-horse helper that parses into one given Option_set in m_s_d_opt_sets (from opt_set()) from the specified file and returns true if and only if successful.

The canonicalize/reject step is not performed: only parsing and (if that worked) final_validator_func(). Recall that final_validator_func() == S_SKIP, even though it will cause us to not have changed that Value_set's Option_set::values_candidate(), is still considered success.

If opt_set->null() == true, this completely no-ops and returns true.

Effect of skip_parsing

It is a pointer. If null (or if opt_set->null() == true), then the behavior is as written.

If not null, then it has the following added effects – used as of this writing by apply_dynamic():

  • If and only if, at entry to method, *skip_parsing == true, then:
    • The contents of the file shall be ignored, and final_validator_func() shall not be called; instead we act as-if the contents were individually valid, but final_validator_func() yielded S_SKIP.
  • On return from method: *skip_parsing shall be set to true if and only if:
    • *skip_parsing = true at entry to method; or else if
    • values in file were individually valid, but final_validator_func() yielded S_SKIP.

More in English: To get the desired SKIP behavior, where a SKIPped Value_set also skips all subsequent (but not preceding) ones, start with bool skip_parsing = false, then pass &skip_parsing to each apply_impl(). (The skip_parsing == nullptr mode is still provided, in case it's useful for something later; as of this writing all the public apply_*() APIs have skip_parsing != nullptr, but it could conceivably change.)

Template Parameters
Value_setSee opt_set().
Parameters
opt_setAn opt_set() result.
baseline_value_set_or_nullIf null then ignored; else – just before attempting to parse and apply values from cfg_path – we will set *opt_set to contain a copy of Value_set *baseline_value_set_or_null. This is to ensure a consistent state after each dynamic config update as opposed to working incrementally.
cfg_pathSee apply_static_or_dynamic_impl().
all_opt_names_or_emptySee apply_static_or_dynamic_impl(). Technically this need not include the ones actually parsed by *opt_set; but including them is harmless (and may be helpful to the caller for simplicity).
final_validator_funcSee apply_static() or apply_dynamic() or apply_static_and_dynamic().
skip_parsingSee above. Recall it can be null.
Returns
true on success; false on failure.

Definition at line 1821 of file cfg_manager.hpp.

References FLOW_LOG_INFO, FLOW_LOG_WARNING, flow::cfg::Option_set< Value_set >::parse_direct_values(), flow::cfg::S_ACCEPT, flow::cfg::S_FAIL, flow::cfg::S_SKIP, flow::cfg::Option_set< Value_set >::values(), and flow::cfg::Option_set< Value_set >::values_candidate().

Here is the call graph for this function:

◆ apply_static() [1/2]

template<typename... S_d_value_set>
template<typename... Value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_static ( allow_invalid_defaults_tag_t  ,
const fs::path &  static_cfg_path,
const typename Final_validator_func< Value_set >::Type &...  final_validator_func,
bool  commit = true 
)

Identical to apply_static() overload without allow_invalid_defaults_tag_t tag; but skips the stringent check on individual defaults' validity.

See also
allow_invalid_defaults_tag_t doc header and/or return-value doc just below.
Template Parameters
Value_setSee other apply_static().
Parameters
static_cfg_pathSee other apply_static().
final_validator_funcSee other apply_static().
commitSee other apply_static().
Returns
See other apply_static(). The difference between the other apply_static() and this overload is: The other overload will return false given an invalid default, even if file static_cfg_path explicitly sets it to a valid value. This tagged overload will not fail (return false for that reason) and will let parsing continue instead.

Definition at line 1632 of file cfg_manager.hpp.

◆ apply_static() [2/2]

template<typename... S_d_value_set>
template<typename... Value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_static ( const fs::path &  static_cfg_path,
const typename Final_validator_func< Value_set >::Type &...  final_validator_func,
bool  commit = true 
)

Invoke this after construction to load the permanent set of static config from config sources including a static config file; if you are also using dynamic config, see apply_static_and_dynamic() as a potential alternative.

See also apply_static() overload with allow_invalid_defaults_tag_t tag.

After this runs and succeeds, assuming commit == true, you may use static_values() and/or all_static_values() to access the loaded values. After this runs and succeeds, but commit == false, you should call apply_static() again 1+ times, with the last such call having commit == true and thus canonicalizing each progressively-built candidate Value_set, so that it becomes available via static_values() and all_static_values().

On failure returns false; else returns true. In the former case the canonical state remains unchanged, and any candidate built-up via preceding commit == false calls (if any) is discarded. The next apply_*() call begins a fresh update.

Tip: On failure you may want to exit program with error; or you can continue knowing that static_values() will return a reference to default values (and all_static_values() will emit pointers to Value_sets with default values) according to Value_set() no-arg ctor (for each Value_set). WARNING(s) logged given failure.

apply_static() will not be tolerant of unknown option names appearing in the config source. The reasoning (and we might change our minds over time): It should be possible to deliver the proper set of static config along with the binary that supports it. However apply_static_and_dynamic() is tolerant of unknown option names.

You must supply exactly S_N_S_VALUE_SETS final_validator_funcs. The compiler will likely require you to explicitly specify the struct types as explicit template args; e.g:

// Explicitly specify the static config struct types. Provide that number of final-validate functions.
// Use null_final_validator_func() for the dummy Null_value_set in particular.
cfg_mgr.apply_static<S_config1, S_config2, Null_value_set>
(file_path, &s_decl_opts1, &s_decl_opts2, null_final_validator_func());
Final_validator_func< Null_value_set >::Type null_final_validator_func()
Returns a value usable as final_validator_func arg to Config_manager::apply_static() and others – for...
Definition: cfg_manager.cpp:31
Empty struct suitable as a *_value_set template arg for Config_manager, when a slot requires a Value_...
Definition: cfg_manager.hpp:73

For context w/r/t commit arg: Please read the section of the class doc header regarding multi-source updates. Corner case note: Formally: invoking apply_Y() immediately after apply_X(commit = false) == true, without reject_candidates() between them, where Y and X differ => apply_Y() shall log an INFO message and invoke reject_candidates() itself; then proceed. In this case apply_Y() is apply_static(). Informally we discourage doing this; it is stylistically better to invoke reject_candidates() explicitly in that scenario which cancels an in-progress update which is unusual though conceivably useful.

Effect of a validator yielding SKIP

Consider Value_sets in their order of declaration, V1, V2, .... If any one of them fails individual option validation, or its final_validator_func() yields FAIL, then we return false indicating this entire update has failed. If all parsed fine, and final_validator_func() yielded ACCEPT for all, then we return true, indicating this update is successful (so far at least, depending on commit).

Now consider the situation where for Value_set Vi, final_validator_func() yielded SKIP, while all Vj before Vi yielded SUCCESS.

Effect 1: The parsed values for that Value_set Vi shall be ignored, as-if they were equal to the cumulative values built in preceding files in this update (or to the baseline values, if this is the first or only file in this update).

Effect 2: The same shall hold for each Value_set Vj after Vi: They will not be parsed; they will not be validated; their final_validator_func() shall not be executed; and they shall ignored, as-if they were equal to the cumulative values built in preceding files in this update (or to the baseline values, if this is the first or only file in this update). So, if (e.g.) V2 is the first Value_set to yield SKIP, then it's as-if V2, V3, ... also yielded SKIP (conceptually speaking) – to the point where their values in the source are ignored entirely.

Note
Each final_validator_func() can be made quite brief by using convenience macro FLOW_CFG_OPT_CHECK_ASSERT(). This will take care of most logging in most cases.
For each Null_value_set: use final_validator_func = null_final_validator_func().
A validator function will not be run if there is a failure to parse any preceding Value_set (including a failure of its associated validator function).
A validator function may rely on all_static_values_candidates() and static_values_candidate() providing the parsed (and validated) values candidate for any preceding static value set.
Todo:
Add support for command line as a config source in addition to file(s), for static config in cfg::Config_manager and cfg::Static_config_manager.
Template Parameters
Value_setThese must be S_d_value_set template param pack args 0, 2, ..., numbering S_N_S_VALUE_SETS in order.
Parameters
static_cfg_pathFile to read.
final_validator_funcFor each arg: If parsing and individual-option-validation succeed, the method shall return success if final_validator_func(V) returns Final_validator_outcome::S_ACCEPT or Final_validator_outcome::S_SKIP, where V is the parsed Value_set. (If apply_static(commit = false) == true call(s) preceded this one, V is the cumulative candidate from such call(s) so far, plus values parsed from static_cfg_path on top of that.) Informally: Please place individual-option validation into FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocations; only use final_validator_func() for internal consistency checks (if any) and skip conditions (if any). Plus: if a final_validatar_func() for a particular Value_set returns S_SKIP, then the subsequent ones will not run (nor will those subsequent Value_sets be parsed or individually validated).
committrue means that this call being successful (returning true) shall cause the promotion of each candidate Value_set built-up so far (via this call and all preceding successful calls with commit == false) to canonical state (accessed via static_values() or all_static_values()). false means that this call being successful shall merely create-and-set (if first such call) or incrementally update (if 2nd, 3rd, ... such call) each candidate Value_set.
Returns
true if and only if successfully parsed config source and validated all settings including final_validator_func() != S_FAIL for all (static) config sets; and defaults were also all individually valid. However, if true but commit == false, then the canonical values (accessed via static_values() or all_static_values()) have not been updated. If false with commit == false, you should not call apply_static() for the planned subsequent config sources: this update has failed, and any built-up candidate Value_sets are discarded.

Definition at line 1622 of file cfg_manager.hpp.

◆ apply_static_and_dynamic() [1/2]

template<typename... S_d_value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_static_and_dynamic ( allow_invalid_defaults_tag_t  ,
const fs::path &  cfg_path,
const typename Final_validator_func< S_d_value_set >::Type &...  final_validator_func,
bool  commit = true 
)

Identical to apply_static_and_dynamic() overload without allow_invalid_defaults_tag_t tag; but skips the stringent check on individual defaults' validity.

See also
allow_invalid_defaults_tag_t doc header and/or return-value doc just below.
Parameters
cfg_pathSee other apply_static_and_dynamic().
final_validator_funcSee other apply_static_and_dynamic().
commitSee other apply_static_and_dynamic().
Returns
See other apply_static_and_dynamic(). The difference between the other apply_static_and_dynamic() and this overload is: The other overload will return false given an invalid default, even if file cfg_path explicitly sets it to a valid value. This tagged overload will not fail (return false for that reason) and will let parsing continue instead.

Definition at line 1950 of file cfg_manager.hpp.

◆ apply_static_and_dynamic() [2/2]

template<typename... S_d_value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_static_and_dynamic ( const fs::path &  cfg_path,
const typename Final_validator_func< S_d_value_set >::Type &...  final_validator_func,
bool  commit = true 
)

If you use dynamic config, and you allow for initial values for dynamic options to be read from the same file as the static config values, then invoke this instead of apply_static().

See also apply_static_and_dynamic() overload with allow_invalid_defaults_tag_t tag.

With static config-only use case:

With dynamic-and-static config use case, with allowing baseline dynamic values to be set in static config file:

With dynamic-and-static config use case, without allowing baseline dynamic values to be set in static config file:

On failure returns false; else returns true. In the former case the canonical state remains unchanged, and any candidate built-up via preceding commit == false calls (if any) is discarded. WARNING(s) logged given failure.

For context w/r/t commit arg: Please read the section of the class doc header regarding multi-source updates. Corner case note: Formally: invoking apply_Y() immediately after apply_X(commit = false) == true, without reject_candidates() between them, where Y and X differ => apply_Y() shall log an INFO message and invoke reject_candidates() itself; then proceed. In this case apply_Y() is apply_static_and_dynamic(). Informally we discourage doing this; it is stylistically better to invoke reject_candidates() explicitly in that scenario which cancels an in-progress update which is unusual though conceivably useful.

Effect of a validator yielding SKIP

Identical to apply_static() but across both static and dynamic Value_sets, in static/dynamic/static/dynamic/... order.

Note
By definition this will not compile unless final_validator_func count equals S_N_VALUE_SETS. However, unlike apply_static() or apply_dynamic(), there are no template args to explicitly supply.
Parameters
cfg_pathFile to read for both static and dynamic config.
final_validator_funcSee apply_static(); particularly the notes about how a validator function will not be run if there is a preceding failure and that all_static_values_candidates() and static_values_candidate() can be relied upon.
committrue means that this call being successful (returning true) shall cause the promotion of each candidate Value_set built-up so far (via this and all preceding successful calls with commit == false) to canonical state (accessed via *ic_values() or all_*ic_values()); and all dynamic change listeners are synchronously called. false means that this call being successful shall merely create-and-set (if first such call) or incrementally update (if 2nd, 3rd, ... such call) each candidate Value_set; no dynamic change listeners are invoked.
Returns
true if and only if successfully parsed config source(s) and validated all settings including final_validator_func() != S_FAIL for all (static and dynamic) config sets; and defaults were also all individually valid. However, if true but commit == false, then the canonical values (accessed via *ic_values() or *ic_values()) have not been updated. If false with commit == false, you should not call apply_static_and_dynamic() for the planned subsequent config sources: this update has failed, and any built-up candidate Value_sets are discarded.

Definition at line 1941 of file cfg_manager.hpp.

◆ apply_static_and_dynamic_impl()

template<typename... S_d_value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_static_and_dynamic_impl ( bool  allow_invalid_defaults,
const fs::path &  cfg_path,
const typename Final_validator_func< S_d_value_set >::Type &...  final_validator_func,
bool  commit 
)
private

Implements all apply_static_and_dynamic() overloads.

Parameters
allow_invalid_defaultstrue if and only if allow_invalid_defaults_tag_t used.
cfg_pathSee apply_static_and_dynamic().
final_validator_funcSee apply_static_and_dynamic().
commitSee apply_static_and_dynamic().
Returns
See apply_static_and_dynamic().

Definition at line 1960 of file cfg_manager.hpp.

References FLOW_LOG_INFO, and FLOW_LOG_WARNING.

◆ apply_static_impl()

template<typename... S_d_value_set>
template<typename... Value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_static_impl ( bool  allow_invalid_defaults,
const fs::path &  static_cfg_path,
const typename Final_validator_func< Value_set >::Type &...  final_validator_func,
bool  commit 
)
private

Implements all apply_static() overloads.

Template Parameters
Value_setSee apply_static().
Parameters
allow_invalid_defaultstrue if and only if allow_invalid_defaults_tag_t used.
static_cfg_pathSee apply_static().
final_validator_funcSee apply_static().
commitSee apply_static().
Returns
See apply_static().

Definition at line 1643 of file cfg_manager.hpp.

References FLOW_LOG_INFO, and FLOW_LOG_WARNING.

◆ apply_static_or_dynamic_impl()

template<typename... S_d_value_set>
template<typename... Value_set>
bool flow::cfg::Config_manager< S_d_value_set >::apply_static_or_dynamic_impl ( bool  dyn_else_st,
const fs::path &  cfg_path,
const boost::unordered_set< std::string > &  all_opt_names_or_empty,
const typename Final_validator_func< Value_set >::Type &...  final_validator_func 
)
private

Work-horse helper that parses either all static value sets or all dynamic value sets from the specified file into m_s_d_opt_sets and returns true if and only if all have succeeded.

(It stops on first failure.) The canonicalize/reject step is not performed: only parsing and (if that worked) final_validator_func(). Recall that final_validator_func() == S_SKIP, even though it will cause us to not have changed that Value_set's Option_set::values_candidate(), is still considered success.

Template Parameters
Value_setSee apply_static() or apply_dynamic() depending on dyn_else_st.
Parameters
dyn_else_stBasically whether apply_static() or apply_dynamic() was called.
cfg_pathSee apply_static() or apply_dynamic() depending on dyn_else_st.
all_opt_names_or_emptyEmpty if any option names may be present in file (such as to support dynamic config compatibility); otherwise must contain any option names that are allowed in this file (such as for apply_static()).
final_validator_funcSee apply_static() or apply_dynamic() depending on dyn_else_st.
Returns
See apply_static() or apply_dynamic() depending on dyn_else_st.

Definition at line 1771 of file cfg_manager.hpp.

◆ d_baseline_value_set()

template<typename... S_d_value_set>
template<typename Value_set >
Value_set * flow::cfg::Config_manager< S_d_value_set >::d_baseline_value_set ( size_t  d_value_set_idx)
private

Helper that obtains the baseline dynamic Value_set in the slot m_d_baseline_value_sets[value_set_idx], which stores a Value_set.

Template Parameters
Value_setS_d_value_set in dynamic slot d_value_set_idx.
Parameters
d_value_set_idxOne of [0, 1, ..., S_N_D_VALUE_SETS).
Returns
Pointer to the Value_set.

Definition at line 1550 of file cfg_manager.hpp.

◆ dynamic_values()

template<typename... S_d_value_set>
template<typename Value_set >
Value_set::Const_ptr flow::cfg::Config_manager< S_d_value_set >::dynamic_values ( size_t  d_value_set_idx) const

Similar to all_dynamic_values() but obtains the dynamic config in one specified slot as opposed to all of them.

Template Parameters
Value_setThis must be S_d_value_set template param pack args in position 1 + 2 * d_value_set_idx (so one of [1, 3, ..., (2 * S_N_D_VALUE_SETS) - 1]). You will need to explicitly specify this tparam. E.g.: const auto cfg_ptr = cfg_mgr.dynamic_values<D_cfg2>(1).
Parameters
d_value_set_idxThe dynamic config slot index; so one of [0, 1, ..., S_N_D_VALUE_SETS).
Returns
Ref-counted pointer to the immutable dynamic config set currently canonical in that slot. Remember: if p returned, then *p is immutable; but another dynamic_values() call may return a different p2 not equal to p.

Definition at line 2572 of file cfg_manager.hpp.

◆ help_to_ostream()

template<typename... S_d_value_set>
void flow::cfg::Config_manager< S_d_value_set >::help_to_ostream ( std::ostream &  os) const

Prints a human-targeted long-form usage message that includes all options with their descriptions and defaults.

This is thread-safe against all concurrent methods on *this and can be invoked anytime after ctor.

Parameters
osStream to which to serialize.

Definition at line 2670 of file cfg_manager.hpp.

◆ help_to_ostream_impl()

template<typename... S_d_value_set>
template<typename Value_set >
void flow::cfg::Config_manager< S_d_value_set >::help_to_ostream_impl ( size_t  value_set_idx,
std::ostream &  os 
) const
private

Helper of help_to_ostream() for m_s_d_opt_sets slot value_set_idx.

Parameters
value_set_idxOne of [0, 1, ..., S_N_VALUE_SETS).
osSee help_to_ostream().

Definition at line 2683 of file cfg_manager.hpp.

◆ invoke_dynamic_change_listeners()

template<typename... S_d_value_set>
void flow::cfg::Config_manager< S_d_value_set >::invoke_dynamic_change_listeners ( size_t  d_value_set_idx,
bool  init 
) const
private

Invokes the registered listeners for the given dynamic config slot (synchronously).

Parameters
d_value_set_idxOne of [0,1, ... S_N_D_VALUE_SETS).
initFor logging (as of this writing): whether this is due to the initial parsing of this dynamic config slot or a subsequent change.

Definition at line 2433 of file cfg_manager.hpp.

References FLOW_LOG_INFO.

◆ log_help()

template<typename... S_d_value_set>
void flow::cfg::Config_manager< S_d_value_set >::log_help ( log::Sev  sev = log::Sev::S_INFO) const

Logs what help_to_ostream() would print.

Parameters
sevSeverity to use for the log message(s).

Definition at line 2700 of file cfg_manager.hpp.

References FLOW_LOG_WITH_CHECKING.

◆ log_state()

template<typename... S_d_value_set>
void flow::cfg::Config_manager< S_d_value_set >::log_state ( log::Sev  sev = log::Sev::S_INFO) const

Logs what state_to_ostream() would print.

This is not thread-safe against several non-const methods.

Parameters
sevSeverity to use for the log message(s).

Definition at line 2658 of file cfg_manager.hpp.

References FLOW_LOG_WITH_CHECKING.

◆ opt_set() [1/2]

template<typename... S_d_value_set>
template<typename Value_set >
Option_set< Value_set > * flow::cfg::Config_manager< S_d_value_set >::opt_set ( size_t  value_set_idx)
private

Helper that obtains the Option_set in the slot m_s_d_opt_sets[value_set_idx], which stores an Option_set<Value_set>.

Template Parameters
Value_setS_d_value_set in slot value_set_idx.
Parameters
value_set_idxOne of [0, 1, ..., S_N_VALUE_SETS).
Returns
Pointer to the Option_set.

Definition at line 1538 of file cfg_manager.hpp.

◆ opt_set() [2/2]

template<typename... S_d_value_set>
template<typename Value_set >
const Option_set< Value_set > * flow::cfg::Config_manager< S_d_value_set >::opt_set ( size_t  value_set_idx) const
private

const overload of the other opt_set() helper.

Template Parameters
Value_setSee other opt_set().
Parameters
value_set_idxSee other opt_set().
Returns
See other opt_set().

Definition at line 1562 of file cfg_manager.hpp.

◆ option_set_canonicalize_or_reject()

template<typename... S_d_value_set>
template<typename Value_set >
void flow::cfg::Config_manager< S_d_value_set >::option_set_canonicalize_or_reject ( Option_set< Value_set > *  opt_set,
bool  canonicalize_else_reject,
bool changed_or_null 
)
private

Helper that executes opt_set->canonicalize_candidate() or opt_set->reject_candidate().

Used in meta-programming. Apply when parsing *opt_set, assuming commit == true.

Template Parameters
Value_setSee opt_set().
Parameters
opt_setAn opt_set() result.
canonicalize_else_rejectSpecifies whether to... well, you know.
changed_or_nullIf not null and canonicalizing (not rejecting), *changed_or_null is set to whether the canonicalize_candidate() detected at least one option's value changed from the previous canonical state.

Definition at line 1569 of file cfg_manager.hpp.

References flow::cfg::Option_set< Value_set >::canonicalize_candidate(), flow::cfg::Option_set< Value_set >::null(), and flow::cfg::Option_set< Value_set >::reject_candidate().

Here is the call graph for this function:

◆ register_dynamic_change_listener()

template<typename... S_d_value_set>
template<typename Value_set >
Config_manager< S_d_value_set... >::On_dynamic_change_func_handle flow::cfg::Config_manager< S_d_value_set >::register_dynamic_change_listener ( size_t  d_value_set_idx,
On_dynamic_change_func &&  on_dynamic_change_func_moved 
)

Saves the given callback; next time apply_dynamic(commit = true) or apply_static_and_dynamic(commit = true) detects at least one changed (or initially set) option value in the specified slot, it will execute this and any other previously registered such callbacks synchronously.

The callbacks will be called after the pointers to be returned by all_dynamic_values() have all been updated.

This is not thread-safe against concurrent calls to itself; nor against concurrent apply_dynamic(commit = true) (against apply_dynamic(commit = false) is okay), apply_static_and_dynamic(commit = true) (against apply_static_and_dynamic(commit = false) is okay), or unregister_dynamic_change_listener().

Note
The callback may optionally be unregistered by using unregister_dynamic_change_listener(). This should be done before anything that the callback accesses is invalidated. If there is a chance that apply*_dynamic() might be called later, then the callback must be unregistered before anything that it accesses is invalidated, otherwise there could be undefined behavior when the callback accesses something which is invalid.
Template Parameters
Value_setSee dynamic_values().
Parameters
d_value_set_idxSee dynamic_values().
on_dynamic_change_func_movedFunction to call synchronously from the next apply*_dynamic() that detects a change.
Returns
A handle which can be used to unregister the callback.

Definition at line 1588 of file cfg_manager.hpp.

References FLOW_LOG_INFO.

◆ reject_candidates()

template<typename... S_d_value_set>
void flow::cfg::Config_manager< S_d_value_set >::reject_candidates

Cancel a not-yet-canonicalized (incomplete) multi-source update, if one is in progress.

If one is not in-progress, INFO-log but otherwise no-op.

This method only has effect following apply_static(), apply_static_and_dynamic(), or apply_dynamic() that

  • returned true; and
  • had arg commit == false.

That is, if there are pending candidate Value_sets that have not yet been upgraded to canonical status via a commit == true apply_*() call, this call discards them, making it as-if no such call(s) were made in the first place.

Informally we do not expect this to be commonly used; typically apply_*() will automatically do this upon encountering an error (in individual option validation or due to Final_validator_outcome::S_FAIL). However if there is some outside reason to abort an ongoing, so-far-successful multi-source update this method will similarly do it.

Definition at line 2366 of file cfg_manager.hpp.

References FLOW_LOG_INFO.

◆ reject_candidates_if_update_type_changed()

template<typename... S_d_value_set>
void flow::cfg::Config_manager< S_d_value_set >::reject_candidates_if_update_type_changed ( Update_type  this_update_type)
private

Helper for the top of apply_*() that guards against a call to apply_Y() following apply_X(commit == false) == true without a reject_candidates() between them.

If this is detected, it effectively "inserts" the "missing" reject_candidates() call – plus an INFO message.

Post-condition: m_multi_src_update_in_progress equals Update_type::S_NONE.

Parameters
this_update_typeThe type (not NONE, or behavior undefined – assert may trip) of apply_*() calling us.

Definition at line 2396 of file cfg_manager.hpp.

References FLOW_LOG_INFO.

◆ save_dynamic_value_set_locked()

template<typename... S_d_value_set>
template<typename Value_set >
void flow::cfg::Config_manager< S_d_value_set >::save_dynamic_value_set_locked ( Option_set< Value_set > *  opt_set,
size_t  d_value_set_idx 
)
private

Little helper that, having assumed m_d_value_sets_mutex is locked, makes a newly allocated copy of the canonical dynamic config at the given slot and saves a (new) ref-counted pointer to it in the proper m_d_value_sets slot.

Template Parameters
Value_setSee opt_set().
Parameters
opt_setAn opt_set() result (dynamic slot).
d_value_set_idxOne of [0, 1, ..., S_N_D_VALUE_SETS).

Definition at line 2418 of file cfg_manager.hpp.

◆ state_to_ostream()

template<typename... S_d_value_set>
void flow::cfg::Config_manager< S_d_value_set >::state_to_ostream ( std::ostream &  os) const

Prints a human-targeted long-form summary of our contents, doubling as a usage message and a dump of current values where applicable.

This is not thread-safe against several non-const methods.

Parameters
osStream to which to serialize.

Definition at line 2585 of file cfg_manager.hpp.

◆ state_to_ostream_impl()

template<typename... S_d_value_set>
template<typename Value_set >
void flow::cfg::Config_manager< S_d_value_set >::state_to_ostream_impl ( size_t  value_set_idx,
std::ostream &  os 
) const
private

Helper of state_to_ostream() for m_s_d_opt_sets slot value_set_idx.

Parameters
value_set_idxOne of [0, 1, ..., S_N_VALUE_SETS).
osSee state_to_ostream().

Definition at line 2598 of file cfg_manager.hpp.

◆ static_values()

template<typename... S_d_value_set>
template<typename Value_set >
const Value_set & flow::cfg::Config_manager< S_d_value_set >::static_values ( size_t  s_value_set_idx) const

Similar to all_static_values(), but obtains the static config in one specified slot as opposed to all of them.

Template Parameters
Value_setThis must be S_d_value_set template param pack args in position 2 * s_value_set_idx (so one of [0, 2, ..., (2 * S_N_S_VALUE_SETS) - 2]). You will need to explicitly specify this tparam. E.g.: const auto cfg_ptr = cfg_mgr.static_values<S_cfg2>(1).
Parameters
s_value_set_idxThe static config slot index; so one of [0, 1, ..., S_N_S_VALUE_SETS).
Returns
The immutable, permanently set static config value set in the s_value_set_idx slot.

Definition at line 2509 of file cfg_manager.hpp.

◆ static_values_candidate()

template<typename... S_d_value_set>
template<typename Value_set >
const Value_set & flow::cfg::Config_manager< S_d_value_set >::static_values_candidate ( size_t  s_value_set_idx) const

Similar to static_values(), but if called from within a validator function passed to apply_static() or apply_static_and_dynamic(), then the parsed values candidate will be returned, instead, if values have been parsed and validated for the static value set but have not yet been applied.

Otherwise, the behavior is equivalent to static_values().

See also
all_static_values_candidates().
Template Parameters
Value_setSee static_values().
Parameters
s_value_set_idxSee static_values().
Returns
See above and static_values().

Definition at line 2518 of file cfg_manager.hpp.

◆ unregister_dynamic_change_listener()

template<typename... S_d_value_set>
void flow::cfg::Config_manager< S_d_value_set >::unregister_dynamic_change_listener ( const On_dynamic_change_func_handle handle)

Remove a previously registered dynamic change callback.

See register_dynamic_change_listener().

This is not thread-safe against concurrent calls to itself; nor against concurrent apply_dynamic(commit = true) (against apply_dynamic(commit = false) is okay), apply_static_and_dynamic(commit = true) (against apply_static_and_dynamic(commit = false) is okay), or register_dynamic_change_listener().

Parameters
handleThe handle which was returned by register_dynamic_change_listener() when the callback was registered.

Definition at line 1609 of file cfg_manager.hpp.

References FLOW_LOG_INFO, flow::cfg::Config_manager< S_d_value_set >::On_dynamic_change_func_handle::m_d_value_set_idx, and flow::cfg::Config_manager< S_d_value_set >::On_dynamic_change_func_handle::m_pos.

Friends And Related Function Documentation

◆ operator<<()

template<typename... S_d_value_set>
std::ostream & operator<< ( std::ostream &  os,
const Config_manager< S_d_value_set... > &  val 
)
related

Serializes (briefly) a Config_manager to a standard output stream.

Template Parameters
S_d_value_setSee Config_manager doc header.
Parameters
osStream to which to serialize.
valValue to serialize.
Returns
os.

Definition at line 2718 of file cfg_manager.hpp.

References flow::cfg::Config_manager< S_d_value_set >::m_nickname.

Member Data Documentation

◆ m_d_baseline_value_sets

template<typename... S_d_value_set>
boost::array<Void_ptr, S_N_D_VALUE_SETS> flow::cfg::Config_manager< S_d_value_set >::m_d_baseline_value_sets
private

The baseline dynamic value sets, in the same order as S_N_D_VALUE_SETS dynamic S_d_value_set template args, the specific type for each slot being S_d_value_set.

The actual type stored is shared_ptr<void>, with each slot's held pointer actually being S_d_value_set*. The same type erasure technique used in m_s_d_opt_sets is used here. d_baseline_value_set() obtains the actual S_d_value_set at a given slot, performing the required casting.

A null pointer is stored if the corresponding Option_set in m_s_d_opt_sets is null() (a placeholder with 0 settings).

Rationale / explanation

Suppose a dynamic Value_set is to be parsed from file F potentially repeatedly, as dynamic updates come in. Then its Option_set from m_s_d_opt_sets[] would parse_config_file(F) each time. However, if that is all we did, then the updates would stack on top of each other incrementally: if one looks at F's contents at a given time, one cannot tell what the Value_set in memory would contain upon parsing it; it would depend on any preceding updates. That's bad: the resulting memory Value_set must be consistent, regardless of what F contained in preceding updates. Therefore, a certain baseline state for that Value_set must be loaded into the Option_set just before the Option_set::parse_config_file() call in each update.

This stores each dynamic slot's baseline Value_set state to load as just described.

After construction this payload is just the default-cted Value_set(). If the user chooses to execute a one-time apply_static_and_dynamic(), then that payload is replaced by the state after having parsed that baseline state. Note, for context, that apply_static_and_dynamic() would be presumably loading not from file F (which can change repeatedly, as dynamic updates come in) but some other file B, typically storing both static value sets' contents as well as the baseline dynamic value sets'.

Definition at line 1346 of file cfg_manager.hpp.

Referenced by flow::cfg::Config_manager< S_d_value_set >::Config_manager().

◆ m_d_value_sets

template<typename... S_d_value_set>
boost::array<Void_ptr, S_N_D_VALUE_SETS> flow::cfg::Config_manager< S_d_value_set >::m_d_value_sets
private

The dynamic config ref-counted handles returned by all_dynamic_values().

Each one is a ref-counted pointer to an immutable copy of the canonical S_d_value_set in that dynamic slot of m_s_d_opt_sets (namely m_s_d_opt_sets[2 * d_idx + 1], where d_idx is in [0, 1, ..., S_N_D_VALUE_SETS)).

The actual type stored is shared_ptr<void>, with each slot's held pointer actually being S_d_value_set::Const_ptr*. The same type erasure technique used in m_s_d_opt_sets is used here.

Protected by m_d_value_sets_mutex.

apply_dynamic() and apply_static_and_dynamic() set these; all_dynamic_values() and dynamic_values() get them.

Rationale

Why is this necessary? Why not just expose a pointer to immutable S_d_value_set directly inside m_s_d_opt_sets[d_idx] as we do (via static_values() and all_static_values()) for the static config? Answer: Because with dynamic config it is important that the user be able to retain (if even for a short time, perhaps to complete some operation – during which *this might process a dynamic update via apply_dynamic()) an immutable S_d_value_set. Since by definition that's not possible with dynamic config updates, we must create a copy. At that point it becomes natural to wrap it in a shared_ptr, so that only one copy is necessary in the entire application – and it'll disappear once (1) the canonical config has been updated, replacing m_d_value_sets[], and (2) all user code has dropped the remaining refs to it.

The ref-counted-pointer-to-dynamic-config is a common pattern. The only thing we add that might not be obvious is having the canonical copy be separate from the one we expose via the pointer publicly. The latter is the natural consequence of the fact that the Value_set managed by Option_set<Value_set> lives inside the Option_set and is not wrapped by any shared_ptr. However Option_set::mutable_values_copy() does provide easy access to a shared_ptr-wrapped copy thereof... so we use it. The perf cost is that of a value-set copy at apply_dynamic() time which is negligible in the grand scheme of things.

Definition at line 1377 of file cfg_manager.hpp.

Referenced by flow::cfg::Config_manager< S_d_value_set >::Config_manager().

◆ m_d_value_sets_mutex

template<typename... S_d_value_set>
util::Mutex_non_recursive flow::cfg::Config_manager< S_d_value_set >::m_d_value_sets_mutex
mutableprivate

Mutex protecting m_d_value_sets.

Definition at line 1380 of file cfg_manager.hpp.

◆ m_dynamic_values_set

template<typename... S_d_value_set>
bool flow::cfg::Config_manager< S_d_value_set >::m_dynamic_values_set
private

Starts false; set to true permanently on successful apply_static_and_dynamic() or apply_dynamic().

Definition at line 1396 of file cfg_manager.hpp.

◆ m_multi_src_update_in_progress

template<typename... S_d_value_set>
Update_type flow::cfg::Config_manager< S_d_value_set >::m_multi_src_update_in_progress
private

In short, truthy if and only if a commit == false update is currently in progress, meaning there's an uncommitted candidate Value_set at the moment.

More precisely:

  • If the last-invoked apply_*() method (1) returned false, or (2) returned true but with arg commit == true, or (3) has never been called, or (4) was followed at any point by reject_candidates(), then this equals Update_type::S_NONE. I.e., no multi-source update is currently in progress. Otherwise:
  • This indicates which apply_*(commit = true) == true method (multi-source update type) it was. To the extent the exact type is irrelevant, its being not NONE indicates a multi-source update is in progress.

Inside an apply_*() method, this is updated to its new value after parsing into m_s_d_opt_sets. Thus during the bulk of such a method's processing we can easily determine whether this is step 1 of a (potentially multi-source) update – rather than step 2+ – as this will equal NONE in the former case only.

When this is not-NONE, an apply_*() impl shall skip a couple of steps it would otherwise perform:

  • Individually-validating the default Value_set() values: Skip, as the first apply_*() in the sequence would have already done it. So it's a waste of compute/entropy.
  • apply_dynamic() applying m_d_baseline_value_sets onto to m_s_d_opt_sets: Skip, as the first apply_*() in the sequence would have already done it. So doing it again would be not only redundant but also destructive, overwriting any incremental changes made in preceding 1+ apply_*() calls.

It would have been enough for it to be a bool for those tests. It is an enum to be able to detect calling apply_X() after apply_Y(commit = false) == true without reject_candidates() in-between.

Definition at line 1424 of file cfg_manager.hpp.

◆ m_nickname

template<typename... S_d_value_set>
const std::string flow::cfg::Config_manager< S_d_value_set >::m_nickname

◆ m_on_dynamic_change_funcs

template<typename... S_d_value_set>
boost::array<std::list<On_dynamic_change_func>, S_N_D_VALUE_SETS> flow::cfg::Config_manager< S_d_value_set >::m_on_dynamic_change_funcs
private

List of callbacks to execute after m_d_value_sets members (the pointers) are next assigned: one per each of the S_N_D_VALUE_SETS dynamic config slots.

Note we do not assign a given slot's m_d_value_sets pointer, unless at least one option has changed within the associated canonical value set in m_s_d_opt_sets.

Rationale

A list<> is used here so that iterators to callbacks can be stored in On_dynamic_change_func_handles without the possibility of them being invalidated by insertions to or removals from the list (which would be the case if vector<> were used).

Definition at line 1393 of file cfg_manager.hpp.

◆ m_s_d_opt_sets

template<typename... S_d_value_set>
boost::array<Void_ptr, S_N_VALUE_SETS> flow::cfg::Config_manager< S_d_value_set >::m_s_d_opt_sets
private

The static and dynamic value sets, in the same order as the S_N_VALUE_SETS S_d_value_set template args, the specific type for each slot being: Option_set<S_d_value_set>.

This is the main data store managed by Config_manager. Each static S_d_value_set (the even slots) is the entirety of data having to do with that piece of static config. Each dynamic S_d_value_set (the odd slots) is the canonical dynamic config; and a pointer to a copy thereof is also maintained in m_d_value_sets.

The slots, in order, are hence:

  • Index [0]: static slot 0,
  • Index [1]: dynamic slot 0,
  • Index [2]: static slot 1,
  • Index [3]: dynamic slot 1,
  • ...
  • Index [.size() - 2]: static slot S_N_S_VALUE_SETS - 1.
  • Index [.size() - 1]: dynamic slot S_N_D_VALUE_SETS - 1.

The actual type stored is shared_ptr<void>, with each slot's held pointer actually being an Option_set<S_d_value_set>*. opt_set() obtains the actual Option_set<> at a given slot, performing the required casting.

Rationale

Why use an array of shared_ptr<void>, which requires a static_cast anytime we need to actually access a config set? The more compile-time/meta-programm-y approach I (ygoldfel) could conceive of was to use tuple<S_d_value_set...>. That would be quite elegant and lightning-fast (though compilation might be very slow). However then, if I understand correctly, that would prevent any kind of runtime iteration through them. This might be absolutely fine, if every slot were treated identically, but in the current setup it's static, dynamic, static, dynamic, .... Therefore, by moving some processing to runtime instead of compile-time we gain flexibility. (Do note my disclaimer in Implementation section in class doc header – indicating this is my first foray into advanced meta-programming like this. So perhaps I lack imagination/experience.)

So then shared_ptr<void> actually stores an S_d_value_set*S_d_value_set being a different type for each slot in the array. This uses the type erasure pattern of standard C++ smart pointers, wherein because static_cast<void*>(T*) is allowed for any type T, shared_ptr<void> deleter will properly delete the T*, as long as the shared_ptr ctor is passed a T* raw pointer. Do note this is type-safe only if our code knows to cast to the proper T* when the pointer is accessed.

Why shared_ptr – not unique_ptr (which is nominally faster in that there's no ref-counting)? Answer: unique_ptr is apparently deliberately simple and lacks aliasing and type-erasure constructors. unique_ptr<T> can only delete a T with the default deleter; and T being void therefore won't compile. It is probably possible to hack it with a custom deleter – but this seemed like overkill, as these handles are never used except inside an array, so reference counting shouldn't even happen and thus cost any perf cycles outside of *this ctor and dtor.

An alternative, which is somewhat more type-safe in that it would have resulted in an RTTI exception if the wrong type-cast were used on the stored value, was any. However any is slower, involving virtual lookups (at least in a naive implementation of any).

Definition at line 1316 of file cfg_manager.hpp.

Referenced by flow::cfg::Config_manager< S_d_value_set >::Config_manager().

◆ S_N_D_VALUE_SETS

template<typename... S_d_value_set>
constexpr size_t flow::cfg::Config_manager< S_d_value_set >::S_N_D_VALUE_SETS = sizeof...(S_d_value_set) / 2
staticconstexpr

The number of dynamic value sets (including any Null_value_sets).

Definition at line 415 of file cfg_manager.hpp.

◆ S_N_S_VALUE_SETS

template<typename... S_d_value_set>
constexpr size_t flow::cfg::Config_manager< S_d_value_set >::S_N_S_VALUE_SETS = sizeof...(S_d_value_set) / 2
staticconstexpr

The number of static value sets (including any Null_value_sets).

Definition at line 413 of file cfg_manager.hpp.

Referenced by flow::cfg::Config_manager< S_d_value_set >::Config_manager().

◆ S_N_VALUE_SETS

template<typename... S_d_value_set>
constexpr size_t flow::cfg::Config_manager< S_d_value_set >::S_N_VALUE_SETS = sizeof...(S_d_value_set)
staticconstexpr

The number of template params in this Config_manager instantiation. It must be even and positive.

Definition at line 408 of file cfg_manager.hpp.

Referenced by flow::cfg::Config_manager< S_d_value_set >::Config_manager().


The documentation for this class was generated from the following files: