Flow 1.0.2
Flow project: Public API.
Classes | Public Types | Public Member Functions | Public Attributes | Static Public Attributes | Related Functions | List of all members
flow::cfg::Config_manager< S_d_value_set > Class Template Reference

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

#include <cfg_manager.hpp>

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

Classes

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

Public Types

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

Public Member Functions

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

Public Attributes

const std::string m_nickname
 See nickname ctor arg.
 

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_sets).
 
static constexpr size_t S_N_D_VALUE_SETS = sizeof...(S_d_value_set) / 2
 The number of dynamic value sets (including any Null_value_sets).
 

Related Functions

(Note that these are not member functions.)

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

Detailed Description

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

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

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

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

When (and how) to use this

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

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

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

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

Config_manager assumes the following setup:

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

In addition, between Config_manager ctor and dtor:

Advanced feature: multi-source parsing and source skipping

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

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

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

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

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

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

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

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

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

However, and this is the key:

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

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

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

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

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

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

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

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

Relationship with the rest of your program/threads

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

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

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

Thread safety

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

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

Member Enumeration Documentation

◆ allow_invalid_defaults_tag_t

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

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

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

Enumerator
S_ALLOW_INVALID_DEFAULTS 

Sole value for tag type allow_invalid_defaults_tag_t.

Constructor & Destructor Documentation

◆ Config_manager()

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

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

See class doc header for class life cycle instructions.

Logging assumption

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

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

Member Function Documentation

◆ all_dynamic_values()

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

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

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

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

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

Performance

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

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

◆ all_static_values()

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

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

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

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

◆ all_static_values_candidates()

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

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

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

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

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

◆ apply_dynamic() [1/2]

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

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

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

◆ apply_dynamic() [2/2]

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

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

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

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

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

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

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

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

Effect of a validator yielding SKIP

Identical to apply_static().

Performance

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

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

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

◆ apply_static() [1/2]

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

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

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

◆ apply_static() [2/2]

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

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

See also apply_static() overload with allow_invalid_defaults_tag_t tag.

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

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

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

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

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

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

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

Effect of a validator yielding SKIP

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

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

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

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

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

◆ apply_static_and_dynamic() [1/2]

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

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

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

◆ apply_static_and_dynamic() [2/2]

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

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

See also apply_static_and_dynamic() overload with allow_invalid_defaults_tag_t tag.

With static config-only use case:

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

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

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

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

Effect of a validator yielding SKIP

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

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

◆ dynamic_values()

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

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

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

◆ help_to_ostream()

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

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

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

Parameters
osStream to which to serialize.

◆ log_help()

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

Logs what help_to_ostream() would print.

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

◆ log_state()

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

Logs what state_to_ostream() would print.

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

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

◆ register_dynamic_change_listener()

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

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

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

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

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

◆ reject_candidates()

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

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

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

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

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

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

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

◆ state_to_ostream()

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

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

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

Parameters
osStream to which to serialize.

◆ static_values()

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

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

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

◆ static_values_candidate()

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

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

Otherwise, the behavior is equivalent to static_values().

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

◆ unregister_dynamic_change_listener()

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

Remove a previously registered dynamic change callback.

See register_dynamic_change_listener().

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

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

Friends And Related Function Documentation

◆ operator<<()

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

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

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

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