Flow 1.0.0
Flow project: Full implementation 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>
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_context & | operator= (const Log_context &src) |
Assignment operator that behaves similarly to the copy constructor. More... | |
Log_context & | operator= (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... | |
Logger * | get_logger () const |
Returns the stored Logger pointer, particularly as many FLOW_LOG_*() macros expect. More... | |
const Component & | get_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_set s). 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_set s). 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_SETS > | m_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_SETS > | m_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_SETS > | m_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_SETS > | m_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... | |
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:
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.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_set
s 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).
Orthogonally, of course, you may want to use it to parse things. In which case:
Config_manager assumes the following setup:
S_value_set
(S for static). S_value_set
must be a type suitable for Option_set as its Value_set
template arg. See Option_set doc header. Spoiler alert: It essentially needs to be a struct
of reasonably-copyable, reasonably-equality-comparable, stream-parseable, stream-printable scalars (possibly nested); it needs to declare its default values in a no-arg ctor; and it needs an option-declaring function that calls FLOW_CFG_OPTION_SET_DECLARE_OPTION() for each data member therein. The latter function must be passed to Config_manager ctor.S_value_set
is accessible by reference-to-immutable accessor static_values() or by all_static_values().S_value_set
and all_static_values() will always emit the same pointers, and the values within the S_value_set
(s) shall never change.D_value_set
, again, must be a type suitable for Option_set as its Value_set
.apply*_dynamic()
: dynamic_values() returns a ref-counted pointer to the heap-allocated D_value_set
canonical at that time. dynamic_values() may return a different pointer each time (and will, if a dynamic change is detected); but the values at a given return pointer will never change. all_dynamic_values() is an alternate approach but essentially the same idea.D_value_set
is stored; apply_dynamic()
makes a single update to it – after a potentially lengthy successful parse of config source(s) – while dynamic_values() makes an atomic read followed by a pointer copy (and returns the copy). The ref-counted-ptr handle ensures the returned D_value_set
survives as long as the user needs it, even if it's immediately replaced by new config within the Config_manager.cm.dynamic_values()->m_some_opt
. If one needs a consistent set of 2 or more values, or if one needs values to not change over time, then one can simply save auto dyn_cfg = cm.dynamic_values()
and then access values through dyn_cfg->
as long as consistency is desired. (Again: *dyn_cfg
can never change, once dyn_cfg
is returned through dynamic_values().)apply*_dynamic()
calls, call register_dynamic_change_listener() for each module that wants to be informed. Each callback so registered will be synchronously executed from apply*_dynamic()
, when a relevant change is detected.To summarize: This is the assumed order of API calls on a given *this
; other orders may lead to undefined behavior:
S_value_set
).S_value_set
s).x
, *x
is immutable; but may return different x
es over time). Alternatively: all_dynamic_values().*ic_values()
call.In addition, between Config_manager ctor and dtor:
register_dynamic_change_listener()
ed earlier may execute synchronously from within apply_dynamic() and/or apply_static_and_dynamic().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)
:
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_set
s 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()
:
R = apply_dynamic(P1, F, false)
; if !R
, update failed/exit algorithm; else:R = apply_dynamic(P2, F, false)
; if !R
, update failed/exit algorithm; else: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:
F(Cnext) == S_ACCEPT
: Modify the candidate by overwriting it: C = Cnext;
.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:
apply_dynamic(..., false)
does not modify the canonical (as returned by all_dynamic_values(), etc.) state. It remains equal to B. Hence no dynamic change listeners are invoked. However:apply_dynamic(..., true)
– makes the canonical state equal the candidate Value_set C
that has been built up. Accordingly, before apply_dynamic(..., true)
returns, if the canonical Value_set B
differs from the candidate Value_set C
that overwrites it, dynamic change listeners are invoked.(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:
if-section-is
option and return SKIP if the machine's actual section does not match it. Else:Value_set
(as usual).Now you've enabled conditional config in a distributed deployment.
_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.
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).
This is discussed in bits and pieces above and in various doc headers. Here is the overall picture however:
*this
:const
method is safe to call concurrently with any other method. Except:apply_dynamic(commit = *)
, as long as either apply_dynamic(commit = true)
or apply_static_and_dynamic(commit = true)
has succeeded at least once prior.S_d_value_set | An even number (at least 2) of settings struct s – 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). |
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.
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 idx
th 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:
(..., 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.
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.
|
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.
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.
|
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 |
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.
|
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.
*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:
*logger_ptr
.*this
only for log_help() (such as in your --help
implementation), you should not let through TRACE-or-more-verbose.logger_ptr | Logger to use for subsequently logging. |
nickname | Brief string used for logging subsequently. |
declare_opts_func_moved | For 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.
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 = *)
.
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.dynamic_values() performance is no worse than: lock mutex, copy S_N_D_VALUE_SETS shared_ptr
s, unlock mutex. See also Performance section of apply_dynamic() doc header.
Value_set | These 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) . |
value_set_or_null | For 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.
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.
Value_set | These 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. |
value_set_or_null | For 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.
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.
Value_set | See all_static_values(). |
value_set_or_null | See all_static_values(). |
Definition at line 2479 of file cfg_manager.hpp.
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.
Value_set | See other apply_dynamic(). |
dynamic_cfg_path | See other apply_dynamic(). |
final_validator_func | See other apply_dynamic(). |
commit | See other apply_dynamic(). |
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.
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.
Identical to apply_static().
dynamic_values() + all_dynamic_values() locking performance is no worse than: lock mutex, assign S_N_D_VALUE_SETS shared_ptr
s, 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.
Value_set | These must be S_d_value_set template param pack args 1, 3, ..., numbering S_N_D_VALUE_SETS in order. |
dynamic_cfg_path | File to read. |
final_validator_func | See apply_static_and_dynamic(). |
commit | true 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. |
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.
|
private |
Implements all apply_dynamic() overloads.
Value_set | See apply_dynamic(). |
allow_invalid_defaults | true if and only if allow_invalid_defaults_tag_t used. |
dynamic_cfg_path | See apply_dynamic(). |
final_validator_func | See apply_dynamic(). |
commit | See apply_static(). |
Definition at line 2176 of file cfg_manager.hpp.
References FLOW_LOG_INFO, and FLOW_LOG_WARNING.
|
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
.
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():
*skip_parsing == true
, then:final_validator_func()
shall not be called; instead we act as-if the contents were individually valid, but final_validator_func()
yielded S_SKIP
.*skip_parsing
shall be set to true
if and only if:*skip_parsing = true
at entry to method; or else iffinal_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.)
Value_set | See opt_set(). |
opt_set | An opt_set() result. |
baseline_value_set_or_null | If 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_path | See apply_static_or_dynamic_impl(). |
all_opt_names_or_empty | See 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_func | See apply_static() or apply_dynamic() or apply_static_and_dynamic(). |
skip_parsing | See above. Recall it can be null. |
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().
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.
Value_set | See other apply_static(). |
static_cfg_path | See other apply_static(). |
final_validator_func | See other apply_static(). |
commit | See other apply_static(). |
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.
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_set
s 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_func
s. The compiler will likely require you to explicitly specify the struct
types as explicit template args; e.g:
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.
Consider Value_set
s 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.
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. final_validator_func = null_final_validator_func()
. Value_set
(including a failure of its associated validator function). Value_set | These must be S_d_value_set template param pack args 0, 2, ..., numbering S_N_S_VALUE_SETS in order. |
static_cfg_path | File to read. |
final_validator_func | For 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_set s be parsed or individually validated). |
commit | true 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 . |
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_set
s are discarded. Definition at line 1622 of file cfg_manager.hpp.
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.
cfg_path | See other apply_static_and_dynamic(). |
final_validator_func | See other apply_static_and_dynamic(). |
commit | See other apply_static_and_dynamic(). |
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.
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.
Identical to apply_static() but across both static and dynamic Value_set
s, in static/dynamic/static/dynamic/... order.
final_validator_func
count equals S_N_VALUE_SETS. However, unlike apply_static() or apply_dynamic(), there are no template args to explicitly supply.cfg_path | File to read for both static and dynamic config. |
final_validator_func | See 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. |
commit | true 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. |
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_set
s are discarded. Definition at line 1941 of file cfg_manager.hpp.
|
private |
Implements all apply_static_and_dynamic() overloads.
allow_invalid_defaults | true if and only if allow_invalid_defaults_tag_t used. |
cfg_path | See apply_static_and_dynamic(). |
final_validator_func | See apply_static_and_dynamic(). |
commit | See apply_static_and_dynamic(). |
Definition at line 1960 of file cfg_manager.hpp.
References FLOW_LOG_INFO, and FLOW_LOG_WARNING.
|
private |
Implements all apply_static() overloads.
Value_set | See apply_static(). |
allow_invalid_defaults | true if and only if allow_invalid_defaults_tag_t used. |
static_cfg_path | See apply_static(). |
final_validator_func | See apply_static(). |
commit | See apply_static(). |
Definition at line 1643 of file cfg_manager.hpp.
References FLOW_LOG_INFO, and FLOW_LOG_WARNING.
|
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.
Value_set | See apply_static() or apply_dynamic() depending on dyn_else_st . |
dyn_else_st | Basically whether apply_static() or apply_dynamic() was called. |
cfg_path | See apply_static() or apply_dynamic() depending on dyn_else_st . |
all_opt_names_or_empty | Empty 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_func | See apply_static() or apply_dynamic() depending on dyn_else_st . |
dyn_else_st
. Definition at line 1771 of file cfg_manager.hpp.
|
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
.
Value_set | S_d_value_set in dynamic slot d_value_set_idx . |
d_value_set_idx | One of [0, 1, ..., S_N_D_VALUE_SETS). |
Value_set
. Definition at line 1550 of file cfg_manager.hpp.
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.
Value_set | This 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) . |
d_value_set_idx | The dynamic config slot index; so one of [0, 1, ..., S_N_D_VALUE_SETS). |
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.
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.
os | Stream to which to serialize. |
Definition at line 2670 of file cfg_manager.hpp.
|
private |
Helper of help_to_ostream() for m_s_d_opt_sets slot value_set_idx
.
value_set_idx | One of [0, 1, ..., S_N_VALUE_SETS). |
os | See help_to_ostream(). |
Definition at line 2683 of file cfg_manager.hpp.
|
private |
Invokes the registered listeners for the given dynamic config slot (synchronously).
d_value_set_idx | One of [0,1, ... S_N_D_VALUE_SETS). |
init | For 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.
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.
sev | Severity to use for the log message(s). |
Definition at line 2700 of file cfg_manager.hpp.
References FLOW_LOG_WITH_CHECKING.
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.
sev | Severity to use for the log message(s). |
Definition at line 2658 of file cfg_manager.hpp.
References FLOW_LOG_WITH_CHECKING.
|
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>.
Value_set | S_d_value_set in slot value_set_idx . |
value_set_idx | One of [0, 1, ..., S_N_VALUE_SETS). |
Definition at line 1538 of file cfg_manager.hpp.
|
private |
|
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
.
Value_set | See opt_set(). |
opt_set | An opt_set() result. |
canonicalize_else_reject | Specifies whether to... well, you know. |
changed_or_null | If 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().
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().
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.Value_set | See dynamic_values(). |
d_value_set_idx | See dynamic_values(). |
on_dynamic_change_func_moved | Function to call synchronously from the next apply*_dynamic() that detects a change. |
Definition at line 1588 of file cfg_manager.hpp.
References FLOW_LOG_INFO.
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
true
; andcommit == false
.That is, if there are pending candidate Value_set
s 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.
|
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.
this_update_type | The 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.
|
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.
Value_set | See opt_set(). |
opt_set | An opt_set() result (dynamic slot). |
d_value_set_idx | One of [0, 1, ..., S_N_D_VALUE_SETS). |
Definition at line 2418 of file cfg_manager.hpp.
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.
os | Stream to which to serialize. |
Definition at line 2585 of file cfg_manager.hpp.
|
private |
Helper of state_to_ostream() for m_s_d_opt_sets slot value_set_idx
.
value_set_idx | One of [0, 1, ..., S_N_VALUE_SETS). |
os | See state_to_ostream(). |
Definition at line 2598 of file cfg_manager.hpp.
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.
Value_set | This 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) . |
s_value_set_idx | The static config slot index; so one of [0, 1, ..., S_N_S_VALUE_SETS). |
s_value_set_idx
slot. Definition at line 2509 of file cfg_manager.hpp.
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().
Value_set | See static_values(). |
s_value_set_idx | See static_values(). |
Definition at line 2518 of file cfg_manager.hpp.
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().
handle | The 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.
|
related |
Serializes (briefly) a Config_manager to a standard output stream.
S_d_value_set | See Config_manager doc header. |
os | Stream to which to serialize. |
val | Value to serialize. |
os
. Definition at line 2718 of file cfg_manager.hpp.
References flow::cfg::Config_manager< S_d_value_set >::m_nickname.
|
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).
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().
|
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.
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().
|
mutableprivate |
Mutex protecting m_d_value_sets.
Definition at line 1380 of file cfg_manager.hpp.
|
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.
|
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:
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: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:
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.
const std::string flow::cfg::Config_manager< S_d_value_set >::m_nickname |
See nickname
ctor arg.
Definition at line 988 of file cfg_manager.hpp.
Referenced by flow::cfg::Config_manager< S_d_value_set >::Config_manager(), and flow::cfg::Config_manager< S_d_value_set >::operator<<().
|
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.
A list<>
is used here so that iterators to callbacks can be stored in On_dynamic_change_func_handle
s 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.
|
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:
.size() - 2
]: static slot S_N_S_VALUE_SETS - 1
..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.
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().
|
staticconstexpr |
The number of dynamic value sets (including any Null_value_set
s).
Definition at line 415 of file cfg_manager.hpp.
|
staticconstexpr |
The number of static value sets (including any Null_value_set
s).
Definition at line 413 of file cfg_manager.hpp.
Referenced by flow::cfg::Config_manager< S_d_value_set >::Config_manager().
|
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().