Flow 1.0.0
Flow project: Public API.
|
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. | |
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. | |
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. | |
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). | |
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). | |
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). |
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. |
|
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() . |
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 . |
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. |
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(). |
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. 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. 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. 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. 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. 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. 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
. 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. |
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). |
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). |
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. |
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.
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. |
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. 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(). |
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. |
|
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
.