Flow 1.0.0
Flow project: Full implementation reference.
cfg_manager.hpp
Go to the documentation of this file.
1/* Flow
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
21#include "flow/cfg/cfg_fwd.hpp"
24#include "flow/util/util.hpp"
26#include <boost/array.hpp>
27
28namespace flow::cfg
29{
30// Types.
31
32/**
33 * Utility/traits type to concisely work with final-validation functions when calling methods like
34 * Config_manager::apply_static().
35 *
36 * @tparam Value_set
37 * See Option_set.
38 */
39template<typename Value_set>
41{
42 // Types.
43
44 /**
45 * Short-hand for a function that takes a parsed config set (meaning all values are individually OK) and returns
46 * what `Config_manager::apply*()` should do to the candidate-for-canonical-state `Value_set` maintained
47 * by Config_manager. Informally its logic should be:
48 * - (Optional) Does `Value_set` indicate that the file that was parsed should be skipped wholesale? If so
49 * yield Final_validator_outcome::S_SKIP. Else:
50 * - If there are no further inter-option problems (given that individually each option value is okay)
51 * yield Final_validator_outcome::S_ACCEPT. Else yield Final_validator_outcome::S_FAIL.
52 */
53 using Type = Function<Final_validator_outcome (const Value_set&)>;
54
55 // Methods.
56
57 /**
58 * Returns a final-validator function that simply always returns Final_validator_outcome::S_ACCEPT.
59 * @return See above.
60 */
61 static Type null();
62}; // struct Final_validator_func
63
64/**
65 * Empty `struct` suitable as a `*_value_set` template arg for Config_manager, when a slot requires a `Value_set`, but
66 * you have no config to actually parse there. In particular, if you have a static `Value_set` but no corresponding
67 * dynamic one, you should provide this as the template arg.
68 *
69 * @see null_declare_opts_func() and null_final_validator_func().
70 */
72 public util::Shared_ptr_alias_holder<boost::shared_ptr<Null_value_set>>
73{
74};
75
76/**
77 * Manages a config setup, intended for a single daemon process, by maintaining
78 * 1 or more set(s) of static config and dynamic config, each, via that number of `Option_set<>`-ready raw value
79 * `struct` types supplied by the user as template arguments. There is an even number of template args (or it will
80 * not compile); each successive pair contains, in order, a *static* `Value_set` followed by a *dynamic* one.
81 * It is possible, for each given pair, to use static config alone or dynamic config alone; in that case pass in
82 * Null_value_set for the unused one:
83 *
84 * ~~~
85 * // First config pair is static-only; second config pair -- for a separate application module perhaps --
86 * // has both a static and dynamic parts.
87 * flow::cfg::Config_manager<Static_config_general, Null_value_set, Static_socket_opts, Dynamic_socket_opts> ...;
88 * ~~~
89 *
90 * @see Static_config_manager for the probably-common use case when you have only static config and only one `struct`
91 * at that. Static_config_manager adapts Config_manager with a very simple API, avoiding parameter packs
92 * and any mention of various dynamic-config complexities.
93 *
94 * @todo Dynamic config support lacks crash rejection; though these features should be an incremental improvement
95 * around the existing code. By crash rejection I mean something like: config file
96 * X comes in; we rename it to X.unparsed; we try to parse it; program crashes -- versus it's fine, so we rename it
97 * X.parsed, and next time X.parsed is what we presumably-safely parse after any restart. Similarly invalid config
98 * may not cause a crash but still shouldn't be repeatedly re-parsed (etc.). Exact design TBD and will be informed
99 * by previous work on other projects (by echan and ygoldfel at least).
100 *
101 * ### When (and how) to use this ###
102 * First see the namespace flow::cfg doc header for a brief overview. If you have chosen to use flow::cfg for (at least
103 * some of) your config needs, what is mandatory to use is the class template Option_set. The choice is to use it
104 * directly (writing your own facilities around it) or to use this Config_manager to maintain a couple of `Option_set`s
105 * for you in a straightforward way. Config_manager supports that one straightforward way, and for daemon programs
106 * it may be a good choice. Otherwise, the idea behind Option_set is to be able to flexibly use them as needed
107 * (Config_manager providing one such way).
108 *
109 * Firstly, you may create a `const` (immutable) Config_manager via its constructor and then just use it to output
110 * a help message (log_help() or help_to_ostream()). This could be used with your program's `--help` option or similar,
111 * and that's it (no parsing takes place).
112 *
113 * ~~~
114 * // Example of a static-config-only Config_manager created just to log the help, and that's it.
115 * flow::cfg::Config_manager<Static_value_set, Null_value_set>
116 * (get_logger(), "cfg", &static_cfg_declare_opts, flow::cfg::null_declare_opts_func())
117 * .log_help();
118 * ~~~
119 *
120 * Orthogonally, of course, you may want to use it to parse things. In which case:
121 *
122 * Config_manager assumes the following setup:
123 * - 1 or more config sets, each applicable perhaps to a different module of your application/library/etc.
124 * For *each* config set, have 1 or both of the following. (Having neither is pointless; so do not. Officially
125 * behavior is undefined if you do this.)
126 * - One set of values, such that it is read from config source(s) once; then no value therein ever changes again.
127 * Call this type `S_value_set` (S for static).
128 * `S_value_set` must be a type suitable for Option_set as its `Value_set` template arg. See Option_set doc
129 * header. Spoiler alert: It essentially needs to be a `struct` of reasonably-copyable,
130 * reasonably-equality-comparable, stream-parseable, stream-printable scalars (possibly nested); it needs to
131 * declare its default values in a no-arg ctor; and it needs an option-declaring function that calls
132 * FLOW_CFG_OPTION_SET_DECLARE_OPTION() for each data member therein. The latter function must be passed to
133 * Config_manager ctor.
134 * - After ctor, static config stored herein will be at its default values. Call apply_static() (or
135 * apply_static_and_dynamic(); see below) to read the static values (from a file, etc.).
136 * - This `S_value_set` is accessible by reference-to-immutable accessor static_values() or by
137 * all_static_values().
138 * - static_values() and all_static_values() are essentially thread-safe, at least after apply_static(), in that
139 * static_values() will always return a reference to the same `S_value_set` and all_static_values() will
140 * always emit the same pointers, and the values within the `S_value_set`(s) shall never change.
141 * - A 2nd set of values, such that it is read from config source(s) at least once; but then potentially
142 * more times, and each time 0 or more of the values may be changed from the original setting.
143 * `D_value_set`, again, must be a type suitable for Option_set as its `Value_set`.
144 * - Initialize the first set of dynamic values by calling either apply_static_and_dynamic() (if your setup allows
145 * for initial dynamic values to be supplied in the same config source(s) (e.g., same file) as static ones)
146 * *and* then apply_dynamic(); or *only* the latter.
147 * - After that initial `apply*_dynamic()`: dynamic_values() returns a ref-counted pointer to the heap-allocated
148 * `D_value_set` canonical at that time. dynamic_values() may return a different pointer each time (and
149 * will, if a dynamic change is detected); but the values *at* a given return pointer will *never* change.
150 * all_dynamic_values() is an alternate approach but essentially the same idea.
151 * - Now, whenever you have reason to believe the dynamic config may have changed, call apply_dynamic().
152 * - apply_dynamic() (after the first one) and dynamic_values() are mutually thread-safe: they can be safely
153 * called concurrently with each other and/or themselves. Internally, an atomic pointer to `D_value_set`
154 * is stored; `apply_dynamic()` makes a single update to it -- after a potentially lengthy successful parse
155 * of config source(s) -- while dynamic_values() makes an atomic read followed by a pointer copy (and
156 * returns the copy). The ref-counted-ptr handle ensures the returned `D_value_set` survives as long
157 * as the user needs it, even if it's immediately replaced by new config within the Config_manager.
158 * - Since dynamic config can change at any time, it is up to the config user how they want to operate on the
159 * ref-counted handle obtained from dynamic_values(). If a potentially new value each time is fine, it's fine
160 * to simply do `cm.dynamic_values()->m_some_opt`. If one needs a consistent set of 2 or more values, or
161 * if one needs values to not change over time, then one can simply save `auto dyn_cfg = cm.dynamic_values()`
162 * and then access values through `dyn_cfg->` as long as consistency is desired. (Again: `*dyn_cfg` can never
163 * change, once `dyn_cfg` is returned through dynamic_values().)
164 * - You may also have hooks executed when dynamic config changes. This is a simple system: before
165 * any relevant `apply*_dynamic()` calls, call register_dynamic_change_listener() for each module that
166 * wants to be informed. Each callback so registered will be *synchronously* executed from `apply*_dynamic()`,
167 * when a relevant change is detected.
168 *
169 * To summarize: This is the assumed order of API calls on a given `*this`; other orders may lead to undefined behavior:
170 * - Config_manager ctor.
171 * - apply_static() or (if using dynamic config, but even so still optional) apply_static_and_dynamic().
172 * - If using dynamic config: apply_dynamic().
173 * - Simultaneously/at various times (thread-safe w/r/t each other):
174 * - static_values() (always returns a reference to the same immutable `S_value_set`).
175 * - all_static_values() (always emits the same pointers to immutable `S_value_set`s).
176 * - If using dynamic config: dynamic_values() (if returned `x`, `*x` is immutable; but may return different `x`es
177 * over time). Alternatively: all_dynamic_values().
178 * - apply_dynamic() (changes ptr returned by dynamic_values() and all_dynamic_values()).
179 * - Config-using modules' destructors/deinitialization code.
180 * - For each such module: Obviously it must run after the last `*ic_values()` call.
181 * - For each such module: It *must* run before Config_manager dtor.
182 * - Config_manager dtor.
183 *
184 * In addition, between Config_manager ctor and dtor:
185 * - (Optional) If using dynamic config: register_dynamic_change_listener() (for each module interested in it);
186 * not thread-safe against concurrent apply_dynamic(), apply_static_and_dynamic(), or
187 * unregister_dynamic_change_listener().
188 * - Functions `register_dynamic_change_listener()`ed earlier may execute synchronously from within
189 * apply_dynamic() and/or apply_static_and_dynamic().
190 * - Such user functions may load async work elsewhere.
191 * - Ensure that thing onto which they load async work exists! See register_dynamic_change_listener() for
192 * useful patterns to ensure this.
193 * - (Optional) If a callback has been registered: unregister_dynamic_change_listener(); not thread-safe against
194 * concurrent apply_dynamic(), apply_static_and_dynamic(), or unregister_dynamic_change_listener().
195 * - Any callback which accesses something which can be invalidated SHOULD be unregistered before that thing becomes
196 * invalid. If there is a chance that apply_dynamic() or apply_static_and_dynamic() might be called after it
197 * has become invalid, then the callback MUST be unregistered (otherwise, undefined behavior could be encountered
198 * when the invalid thing is accessed).
199 *
200 * ### Advanced feature: multi-source parsing and source skipping ###
201 * By default this feature is not in play, as the `bool commit` arg to apply_static_and_dynamic(), apply_static(),
202 * and apply_dynamic() defaults to `true`. The multi-source feature is enabled by the user setting it to `false`
203 * for some calls to `apply_*()`. To explain how it works consider one particular `Value_set` and an attempt
204 * to execute one of `apply_*()` methods w/r/t that `Value_set` (among potentially others to which the same
205 * discussion applies equally). For purposes of discussion assume it is a dynamic `Value_set`, and the operation
206 * in question is apply_dynamic(); the same points apply to static ones and the other two `apply_*()` calls
207 * except where noted below.
208 *
209 * Suppose the canonical (as returned by dynamic_values() or all_dynamic_values()) `Value_set` is in state B,
210 * the baseline state. (If `Value_set` is static, then B is the default state from default-cted `Value_set`, and
211 * the canonical-state accessors are static_values() and all_static_values().)
212 * Normally a single *update* consists of just one call to `apply_dynamic(commit = true)`:
213 * -# `R = apply_dynamic(P, F, true)`, where `P` is a file path, and `F` is your Final_validator_func::Type function.
214 *
215 * If `F() == Final_validator_outcome::S_FAIL`, then `R == false`; state stays at B.
216 * If it's `F() == S_ACCEPT` (and the individual options were all fine), then `R == true`; state becomes B';
217 * and if B does not equal B' (a change was detected), then the dynamic changes listener(s)
218 * (from register_dynamic_change_listener()) are synchronously called before apply_dynamic() returns.
219 * (That listener stuff does not apply to static `Value_set`s in apply_static() and apply_static_and_dynamic().)
220 *
221 * That's the default behavior without engaging this feature. To engage the feature, one has to
222 * call `apply_dynamic(commit = false)` at least once. A single *update* now consists of 2+ calls
223 * to `apply_dynamic()`:
224 * -# `R = apply_dynamic(P1, F, false)`; if `!R`, update failed/exit algorithm; else:
225 * -# `R = apply_dynamic(P2, F, false)`; if `!R`, update failed/exit algorithm; else:
226 * -# ...
227 * -# `R = apply_dynamic(Pn, F, true)`; if `!R`, update failed/exit algorithm; else done.
228 * The `true` arg indicates the final call.
229 *
230 * (`F` can be different each time, though informally we suspect that would be unorthodox. It is allowed formally.)
231 *
232 * Each apply_dynamic() call builds up a *candidate* `Value_set C` which is created (at the top of call 1)
233 * to equal baseline/initial state B and then potentially incrementally morphed by each apply_dynamic() call.
234 * Assuming no error (from individual validator or an `F() == S_FAIL`) at any stage, the effect on the *candidate*
235 * `Value_set C` is as follows:
236 * -# If `F(Cnext) == S_ACCEPT`: Modify the *candidate* by overwriting it: `C = Cnext;`.
237 * -# If `F(Cnext) == S_SKIP`: Keep the *candidate* `Value_set C` unchanged from its current value at entry to that
238 * apply_dynamic() call. `Cnext` is discarded (so the incremental update to the candidate is *skipped*).
239 *
240 * However, and this is the key:
241 * - `apply_dynamic(..., false)` does *not* modify the canonical (as returned by all_dynamic_values(), etc.)
242 * state. It remains equal to B. Hence no dynamic change listeners are invoked. However:
243 * - the final call -- `apply_dynamic(..., true)` -- makes the canonical state equal the *candidate* `Value_set C`
244 * that has been built up. Accordingly, before `apply_dynamic(..., true)` returns, if the canonical
245 * `Value_set B` differs from the candidate `Value_set C` that overwrites it, dynamic change listeners are
246 * invoked.
247 *
248 * (Hence the one-call update scenario (the vanilla one) is merely a degenerate case of the multi-call scenario;
249 * as then the last call is the only call and thus canonicalizes the candidate and calls dynamic change listeners if
250 * appropriate.)
251 *
252 * If at any stage `F(Cnext)` yields Final_validator_outcome::S_FAIL, then apply_dynamic() returns `false`. This means
253 * the entire update has failed; the *candidate* is abandoned, and you should not make the subsequent
254 * apply_dynamic() calls that may be remaining in the update. The next such call will be taken to be the 1st
255 * of another update, with a fresh candidate C created to equal B.
256 *
257 * Why use this? Why use 2+ files for a given update? Indeed you shouldn't, unless there is specifically a good
258 * reason. What might that be? The precipitating use case, and the basic one immediately obvious to the authors,
259 * is where exactly for 1 of the files the final-validator shall return Final_validator_outcome::S_ACCEPT, while
260 * for *all* others it will return Final_validator_outcome::S_SKIP. For example, if your distributed network
261 * consists of 3 sections, and each machine knows to which section it belongs, and each file is aimed at exactly 1
262 * of the 3 and thus specifies it as some option `if-section-is=` (possible values 1 or 2 or 3), then the
263 * final-validator func would:
264 * -# Check `if-section-is` option and return SKIP if the machine's actual section does not match it. Else:
265 * -# Ensure internal consistency of candidate `Value_set` (as usual).
266 * -# Return FAIL or ACCEPT depending on the answer to that (as usual).
267 *
268 * Now you've enabled conditional config in a distributed deployment.
269 *
270 * @warning In this *conditional config* use case, it is likely best to declare any given conditional option
271 * using a `_NO_ACC` variation of your `FLOW_CFG_OPTION_SET_DECLARE_OPTION*()` option of choice;
272 * that is `FLOW_CFG_OPTION_SET_DECLARE_OPTION_NO_ACC()` or similarly-postfixed variation.
273 * You probably do not want such an option's value to persist from an earlier file in the update to a later
274 * one; instead it should probably reset to its default "at the top" of each file; `_NO_ACC` variants
275 * will accomplish this.
276 *
277 * That said the use of SKIP (for all but 1) is not mandatory in such a multi-file-update setup. You can never-SKIP,
278 * or you can ACCEPT (not-SKIP) 2+ files; it is conceivable those setups have use cases as well. Informally though we
279 * reason to you as follows:
280 * - If you never SKIP, then why complicate matters by having updates consist of 2+ files in the first place?
281 * Isn't it simpler (for config deployment and other aspects of the system) to just keep it to 1?
282 * - If you ACCEPT (don't-SKIP) 2+ files, then merely looking at a given file will no longer easily show the resulting
283 * final config, as each individual update is now incremental in nature; a preceding file in the same update
284 * may have changed (versus baseline) some setting not mentioned in that particular file. This may prove to be
285 * confusing.
286 * - If you ACCEPT (don't-SKIP) exactly 1 file, then this no longer the case, and things are simple enough again.
287 * - If you ACCEPT (don't-SKIP) 2+ files, then to mitigate the problem where the final config is not quite clear
288 * just by looking at the input files, it's simple to use state_to_ostream() or log_state() to direct the
289 * result to a file or the logs, for human viewing.
290 *
291 * The above are informal recommendations for maximum simplicity or clarity, but it is entirely conceivable a more
292 * complex situation arises and requires they not be heeded.
293 *
294 * ### Relationship with the rest of your program/threads ###
295 * At this time Config_manager is deliberately simple in that it doesn't start any threads of its own; it doesn't watch
296 * file modifications to detect dynamic changes; and its methods can be called from any thread and always act
297 * entirely synchronously. (The only methods affected by thread considerations are apply_dynamic() and
298 * dynamic_values() + all_dynamic_values(); they are intentionally thread-safe w/r/t each other.)
299 *
300 * To the extent that it uses callbacks -- again, only for dynamic config needs and only optionally -- they, too, are
301 * executed synchronously. However, these callbacks can (of course) `.post()` work onto boost.asio util::Task_engine
302 * or async::Concurrent_task_loop or async::Single_thread_task_loop.
303 *
304 * Informally:
305 * It is, however, possible and perhaps probable that one should build more facilities to detect cfg changes of various
306 * kinds; for this thread(s) may be useful. We suspect it is better to build class(es) that do that and make use of
307 * Config_manager -- not expand Config_manager to do such things. Its simplicity and relative light weight and
308 * flexibility are good properties to conserve and not saddle with baggage (even optional baggage).
309 *
310 * ### Thread safety ###
311 * This is discussed in bits and pieces above and in various doc headers. Here is the overall picture however:
312 * - Separate Config_manager objects are independent of each other, so there are no thread safety issues.
313 * - For a given `*this`:
314 * - By default, no non-`const` method is safe to call concurrently with any other method. Except:
315 * - dynamic_values() / all_dynamic_values() may be called concurrently with itself/each other or any other method
316 * including `apply_dynamic(commit = *)`, as long as either `apply_dynamic(commit = true)` or
317 * `apply_static_and_dynamic(commit = true)` has succeeded at least once prior.
318 *
319 * @tparam S_d_value_set
320 * An even number (at least 2) of settings `struct`s -- see Option_set doc header for requirements for each --
321 * in order static, dynamic, static, dynamic, .... Use Null_value_set as needed; but do not use Null_value_set
322 * for both the static and dynamic parameter in a given pair (e.g., `<Null_value_set, Null_value_set, ...>`
323 * is both useless and formally disallowed).
324 *
325 * @internal
326 * ### Implementation notes ###
327 * I (ygoldfel) wrote this, originally, in 2 stages. At first it only supported exactly 2 template args: one static
328 * and one dynamic `Value_set`. In the next stage I turned it into a parameter pack, so that 4, 6, 8, ... args
329 * can be provided if desired -- to support multiple configurable modules in the application. Before this I had done
330 * some basic parameter-pack coding, but undoubtedly Config_manager is my first foray into this level of
331 * sophistication. Certainly I welcome changes to improve clarity of the implementation and other suggestions.
332 * Generally speaking the code does strike me as complex, though there's a good case to be made to the effect of:
333 * That is just the nature of the beast. It's meta-programming: having the compiler perform loops, of a sort,
334 * at *compile time*. Because `S_d_value_set...` is not homogenous -- within each adjacent pair of tparams the first
335 * and the second are *not* always treated equivalently -- the code has to be tricky at times.
336 *
337 * That said the usability of the API should not be affected by these details much. At times one has to supply
338 * additional `<template args>` -- apply_static() and apply_dynamic() come to mind in particular -- to help the
339 * compiler; but the compiler will complain about being unable to deduce the tparams, and then the next action for the
340 * user is straightforward.
341 *
342 * ### A particular trick explained ###
343 * In C++17 fold expressions are available (https://en.cppreference.com/w/cpp/language/fold), and we use them
344 * to create compile-time loops without the appalling coding/maintenance overhead of the meta-recursive pattern.
345 * (This replaces a much uglier trick involving a dummy `int[]` initializer, though that was still superior to
346 * doing the meta-recursive thing all over the place.)
347 *
348 * Suppose, say, you want to call `f<Value_set>(idx, dyn_else_st)`,
349 * where `idx` runs through [0, #S_N_VALUE_SETS), while `dyn_else_st` alternates between `false` (static) and
350 * `true` (dynamic). E.g., `f()` might parse the `idx`th slot of #m_s_d_opt_sets and do something extra if it's
351 * an odd (dynamic) slot. Note `f()` is a template parameterized on `Value_set`. Then:
352 *
353 * ~~~
354 * size_t idx = 0;
355 * bool dyn_else_st = false;
356 * (
357 * ..., // This indicates the entire (..., expr) is a fold-expression, where...
358 * (
359 * // ...the `S_d_value_set` in the following expression is the kernel of the param-pack expansion;
360 * // due to the fact the class template is: template<typename... S_d_value_set> class Config_manager
361 * f<S_d_value_set>(idx, dyn_else_st),
362 * ++idx,
363 * dyn_else_st = !dyn_else_st)
364 * )
365 * );
366 * ~~~
367 *
368 * `(..., expr)` expands to, ultimately, `(expr1, expr2, ..., expr3)`, where each `expr...i...` is `expr` with
369 * the i-th `S_d_value_set`. In our case `expr...i...` is itself a comma-expression with 3 sub-expressions.
370 * Comma-expression evaluates left-to-right, so at runtime the side effects of each
371 * `expr...i...` are occurring at runtime; in this case this includes incrementing `idx` and repeatedly flipping
372 * `dyn_else_st`.
373 *
374 * In the actual code, often we don't (just) call some helper `f<>()` but rather actually perform some steps in-line
375 * there instead -- similarly to how this example does a couple of simple expressions after calling `f<>()`.
376 * The good thing is the reader need not jump to another function (and all that maintenance overhead). The bad thing
377 * is the language constructions within an expression in C++ (not a functional language) are limited. Loops aren't
378 * possible; `if ()`s have to be replaced by `?:` ternaries or `&&` or `||` expressions; and so on.
379 * It's not always the most readable thing, if one isn't used to it, but the author feels it's doable to get used to it,
380 * and it's worth it for the brevity.
381 */
382template<typename... S_d_value_set>
384 public log::Log_context
385{
386public:
387 // Types.
388
389 /**
390 * Tag type: indicates an `apply_*()` method must *allow* invalid defaults and only complain if the config source
391 * does not explicitly supply a valid value. Otherwise the defaults themselves are also stringently checked
392 * regardless of whether they are overridden. This setting applies only to individual-option-validators.
393 * Final_validator_func validation is orthogonal to this.
394 */
396 {
397 S_ALLOW_INVALID_DEFAULTS ///< Sole value for tag type #allow_invalid_defaults_tag_t.
398 };
399
400 /// Short-hand for a callback to execute on dynamic config change.
402
404
405 // Constants.
406
407 /// The number of template params in this Config_manager instantiation. It must be even and positive.
408 static constexpr size_t S_N_VALUE_SETS = sizeof...(S_d_value_set);
409
410 static_assert((S_N_VALUE_SETS % 2) == 0,
411 "Must have even number of template args to Config_manager; use a trailing Null_value_set if needed.");
412 /// The number of static value sets (including any `Null_value_set`s).
413 static constexpr size_t S_N_S_VALUE_SETS = sizeof...(S_d_value_set) / 2;
414 /// The number of dynamic value sets (including any `Null_value_set`s).
415 static constexpr size_t S_N_D_VALUE_SETS = sizeof...(S_d_value_set) / 2;
416
417 // Constructors/destructor.
418
419 /**
420 * Constructs a Config_manager ready to read initial config via `apply_*()` and other setup methods; and further
421 * capable of both static and dynamic config. See class doc header for class life cycle instructions.
422 *
423 * ### Logging assumption ###
424 * `*logger_ptr` is a standard logging arg. Note, though, that the class will assume that log verbosity may not have
425 * yet been configured -- since this Config_manager may be the thing configuring it. Informal recommendations:
426 * - You should let through INFO and WARNING messages in `*logger_ptr`.
427 * - If you plan to use `*this` only for log_help() (such as in your `--help` implementation), you should
428 * *not* let through TRACE-or-more-verbose.
429 * - Once (and if) you engage any actual parsing (apply_static(), etc.), TRACE may be helpful
430 * in debugging as usual.
431 *
432 * @param logger_ptr
433 * Logger to use for subsequently logging.
434 * @param nickname
435 * Brief string used for logging subsequently.
436 * @param declare_opts_func_moved
437 * For each `S_d_value_set`, in order, the declare-options callback as required by
438 * `Option_set<S_d_value_set>` constructor; see its doc header for instructions.
439 * For each Null_value_set: use the function returned by `null_declare_opts_func()`.
440 */
441 explicit Config_manager(log::Logger* logger_ptr, util::String_view nickname,
442 typename Option_set<S_d_value_set>::Declare_options_func&&... declare_opts_func_moved);
443
444 // Methods.
445
446 /**
447 * Invoke this after construction to load the permanent set of static config from config sources including
448 * a static config file; if you are also using dynamic config, see apply_static_and_dynamic() as a *potential*
449 * alternative. See also apply_static() overload with #allow_invalid_defaults_tag_t tag.
450 *
451 * After this runs and succeeds, assuming `commit == true`, you may use static_values() and/or
452 * all_static_values() to access the loaded values. After this runs and succeeds, but `commit == false`,
453 * you should call apply_static() again 1+ times, with the last such call having `commit == true` and thus
454 * canonicalizing each progressively-built candidate `Value_set`, so that it becomes available via static_values()
455 * and all_static_values().
456 *
457 * On failure returns `false`; else returns `true`. In the former case the canonical state remains unchanged,
458 * and any candidate built-up via preceding `commit == false` calls (if any) is discarded. The next
459 * `apply_*()` call begins a fresh update.
460 *
461 * Tip: On failure you may want to exit program with error; or you can continue knowing that
462 * static_values() will return a reference to default values (and all_static_values() will emit pointers to
463 * `Value_set`s with default values) according to `Value_set()` no-arg ctor (for each `Value_set`).
464 * WARNING(s) logged given failure.
465 *
466 * apply_static() will *not* be tolerant of unknown option names appearing in the config source. The reasoning
467 * (and we might change our minds over time): It should be possible to deliver the proper set of static config
468 * along with the binary that supports it. However apply_static_and_dynamic() is tolerant of unknown option names.
469 *
470 * You must supply exactly #S_N_S_VALUE_SETS `final_validator_func`s. The compiler will likely require you to
471 * explicitly specify the `struct` types as explicit template args; e.g:
472 *
473 * ~~~
474 * Config_manager<S_config1, D_config1, S_config2, D_config2, Null_value_set, D_config3> cfg_mgr(...);
475 * // Explicitly specify the static config struct types. Provide that number of final-validate functions.
476 * // Use null_final_validator_func() for the dummy Null_value_set in particular.
477 * cfg_mgr.apply_static<S_config1, S_config2, Null_value_set>
478 * (file_path, &s_decl_opts1, &s_decl_opts2, null_final_validator_func());
479 * ~~~
480 *
481 * For context w/r/t `commit` arg: Please read the section of the class doc header regarding multi-source
482 * updates. Corner case note: Formally: invoking `apply_Y()` immediately after `apply_X(commit = false) == true`,
483 * without reject_candidates() between them, where Y and X differ => `apply_Y()` shall log an INFO message
484 * and invoke reject_candidates() itself; then proceed. In this case `apply_Y()` is apply_static().
485 * Informally we discourage doing this; it is stylistically better to invoke reject_candidates()
486 * explicitly in that scenario which cancels an in-progress update which is unusual though conceivably useful.
487 *
488 * ### Effect of a validator yielding SKIP ###
489 * Consider `Value_set`s in their order of declaration, V1, V2, .... If any one of them fails individual
490 * option validation, or its `final_validator_func()` yields FAIL, then we return `false`
491 * indicating this entire update has failed. If all parsed fine, and `final_validator_func()` yielded ACCEPT for all,
492 * then we return `true`, indicating this update is successful (so far at least, depending on `commit`).
493 *
494 * Now consider the situation where for `Value_set` Vi, `final_validator_func()` yielded SKIP, while all
495 * Vj before Vi yielded SUCCESS.
496 *
497 * Effect 1: The parsed values for that `Value_set` Vi shall be ignored, as-if they were equal to the cumulative
498 * values built in preceding files in this update (or to the baseline values, if this is the first or only
499 * file in this update).
500 *
501 * Effect 2: The same shall hold for *each* `Value_set` Vj *after* Vi: They will not be parsed; they will not be
502 * validated; their `final_validator_func()` shall not be executed; and they shall ignored, as-if they were equal
503 * to the cumulative values built in preceding files in this update (or to the baseline values, if this is the
504 * first or only file in this update). So, if (e.g.) V2 is the first `Value_set` to yield SKIP, then
505 * it's as-if V2, V3, ... also yielded SKIP (conceptually speaking) -- to the point where their values in
506 * the source are ignored entirely.
507 *
508 * @note Each `final_validator_func()` can be made quite brief by using convenience macro
509 * FLOW_CFG_OPT_CHECK_ASSERT(). This will take care of most logging in most cases.
510 * @note For each Null_value_set: use `final_validator_func = null_final_validator_func()`.
511 * @note A validator function will not be run if there is a failure to parse any preceding `Value_set` (including a
512 * failure of its associated validator function).
513 * @note A validator function may rely on all_static_values_candidates() and static_values_candidate() providing the
514 * parsed (and validated) values candidate for any preceding static value set.
515 *
516 * @todo Add support for command line as a config source in addition to file(s), for static config in
517 * cfg::Config_manager and cfg::Static_config_manager.
518 *
519 * @tparam Value_set
520 * These must be `S_d_value_set` template param pack args 0, 2, ..., numbering
521 * #S_N_S_VALUE_SETS in order.
522 * @param static_cfg_path
523 * File to read.
524 * @param final_validator_func
525 * For each arg:
526 * If parsing and individual-option-validation succeed, the method shall return success if
527 * `final_validator_func(V)` returns Final_validator_outcome::S_ACCEPT or Final_validator_outcome::S_SKIP,
528 * where V is the parsed `Value_set`. (If `apply_static(commit = false) == true` call(s) preceded this
529 * one, V is the cumulative candidate from such call(s) so far, plus values parsed from
530 * `static_cfg_path` on top of that.)
531 * Informally: Please place individual-option validation
532 * into FLOW_CFG_OPTION_SET_DECLARE_OPTION() invocations; only use `final_validator_func()` for
533 * internal consistency checks (if any) and skip conditions (if any).
534 * Plus: if a `final_validatar_func()` for a particular `Value_set` returns `S_SKIP`, then
535 * the subsequent ones will not run (nor will those subsequent `Value_set`s be parsed or individually
536 * validated).
537 * @param commit
538 * `true` means that this call being successful (returning `true`) shall cause the promotion of
539 * each candidate `Value_set` built-up so far (via this call and all preceding successful calls with
540 * `commit == false`) to canonical state (accessed via static_values() or all_static_values()).
541 * `false` means that this call being successful shall merely create-and-set (if first such call)
542 * or incrementally update (if 2nd, 3rd, ... such call) each candidate `Value_set`.
543 * @return `true` if and only if successfully parsed config source and validated all settings including
544 * `final_validator_func() != S_FAIL` for *all* (static) config sets; and defaults were also all individually
545 * valid. However, if `true` but `commit == false`, then the canonical values
546 * (accessed via static_values() or all_static_values()) have not been updated.
547 * If `false` with `commit == false`, you should not call apply_static() for the planned
548 * subsequent config sources: this update has failed, and any built-up candidate `Value_set`s are
549 * discarded.
550 */
551 template<typename... Value_set>
552 bool apply_static(const fs::path& static_cfg_path,
553 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
554 bool commit = true);
555
556 /**
557 * Identical to apply_static() overload without #allow_invalid_defaults_tag_t tag; but skips the stringent
558 * check on individual defaults' validity.
559 *
560 * @see #allow_invalid_defaults_tag_t doc header and/or return-value doc just below.
561 *
562 * @tparam Value_set
563 * See other apply_static().
564 * @param static_cfg_path
565 * See other apply_static().
566 * @param final_validator_func
567 * See other apply_static().
568 * @param commit
569 * See other apply_static().
570 * @return See other apply_static(). The difference between the other `apply_static()` and this overload is:
571 * The other overload will return `false` given an invalid default, even if
572 * file `static_cfg_path` explicitly sets it to a valid value. This tagged overload will not fail (return
573 * `false` for that reason) and will let parsing continue instead.
574 */
575 template<typename... Value_set>
577 const fs::path& static_cfg_path,
578 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
579 bool commit = true);
580
581 /**
582 * If you use dynamic config, *and* you allow for initial values for dynamic options to be read from the same file
583 * as the static config values, then invoke this instead of apply_static(). See also apply_static_and_dynamic()
584 * overload with #allow_invalid_defaults_tag_t tag.
585 *
586 * With static config-only use case:
587 * - Just apply_static().
588 * - Then static_values() / all_static_values() whenever needed.
589 *
590 * With dynamic-and-static config use case, *with* allowing baseline dynamic values to be set in static config
591 * file:
592 * - apply_static_and_dynamic(). <-- ATTN
593 * - Initial apply_dynamic(). (Typically it is allowed that there is no dynamic config file present yet though.)
594 * - Then static_values() / all_static_values(), dynamic_values() / all_dynamic_values() whenever needed.
595 * - Further apply_dynamic(), if dynamic config source(s) may have changed.
596 *
597 * With dynamic-and-static config use case, *without* allowing baseline dynamic values to be set in static config
598 * file:
599 * - apply_static().
600 * - Initial apply_dynamic().
601 * - Then static_values() / all_static_values(), dynamic_values() / all_dynamic_values() whenever needed.
602 * - Further apply_dynamic(), if dynamic config source(s) may have changed.
603 *
604 * On failure returns `false`; else returns `true`. In the former case the canonical state remains unchanged,
605 * and any candidate built-up via preceding `commit == false` calls (if any) is discarded. WARNING(s) logged
606 * given failure.
607 *
608 * For context w/r/t `commit` arg: Please read the section of the class doc header regarding multi-source
609 * updates. Corner case note: Formally: invoking `apply_Y()` immediately after `apply_X(commit = false) == true`,
610 * without reject_candidates() between them, where Y and X differ => `apply_Y()` shall log an INFO message
611 * and invoke reject_candidates() itself; then proceed. In this case `apply_Y()` is apply_static_and_dynamic().
612 * Informally we discourage doing this; it is stylistically better to invoke reject_candidates()
613 * explicitly in that scenario which cancels an in-progress update which is unusual though conceivably useful.
614 *
615 * ### Effect of a validator yielding SKIP ###
616 * Identical to apply_static() but across both static and dynamic `Value_set`s, in static/dynamic/static/dynamic/...
617 * order.
618 *
619 * @note By definition this will not compile unless `final_validator_func` count equals #S_N_VALUE_SETS.
620 * However, unlike apply_static() or apply_dynamic(), there are no template args to explicitly supply.
621 *
622 * @param cfg_path
623 * File to read for both static and dynamic config.
624 * @param final_validator_func
625 * See apply_static(); particularly the notes about how a validator function will not be run if there is a
626 * preceding failure and that all_static_values_candidates() and static_values_candidate() can be relied upon.
627 * @param commit
628 * `true` means that this call being successful (returning `true`) shall cause the promotion of
629 * each candidate `Value_set` built-up so far (via this and all preceding successful calls with
630 * `commit == false`) to canonical state (accessed via `*ic_values()` or `all_*ic_values()`);
631 * and all dynamic change listeners are synchronously called.
632 * `false` means that this call being successful shall merely create-and-set (if first such call)
633 * or incrementally update (if 2nd, 3rd, ... such call) each candidate `Value_set`; no dynamic change
634 * listeners are invoked.
635 * @return `true` if and only if successfully parsed config source(s) and validated all settings including
636 * `final_validator_func() != S_FAIL` for *all* (static and dynamic) config sets; and defaults were also all
637 * individually valid. However, if `true` but `commit == false`, then the canonical values
638 * (accessed via `*ic_values()` or `*ic_values()`) have not been updated.
639 * If `false` with `commit == false`, you should not call apply_static_and_dynamic() for the planned
640 * subsequent config sources: this update has failed, and any built-up candidate `Value_set`s are
641 * discarded.
642 */
643 bool apply_static_and_dynamic(const fs::path& cfg_path,
644 const typename Final_validator_func<S_d_value_set>::Type&... final_validator_func,
645 bool commit = true);
646
647 /**
648 * Identical to apply_static_and_dynamic() overload without #allow_invalid_defaults_tag_t tag; but skips the stringent
649 * check on individual defaults' validity.
650 *
651 * @see #allow_invalid_defaults_tag_t doc header and/or return-value doc just below.
652 *
653 * @param cfg_path
654 * See other apply_static_and_dynamic().
655 * @param final_validator_func
656 * See other apply_static_and_dynamic().
657 * @param commit
658 * See other apply_static_and_dynamic().
659 * @return See other apply_static_and_dynamic(). The difference between the other `apply_static_and_dynamic()` and
660 * this overload is:
661 * The other overload will return `false` given an invalid default, even if
662 * file `cfg_path` explicitly sets it to a valid value. This tagged overload will not fail (return
663 * `false` for that reason) and will let parsing continue instead.
664 */
666 const fs::path& cfg_path,
667 const typename Final_validator_func<S_d_value_set>::Type&... final_validator_func,
668 bool commit = true);
669
670 /**
671 * Load the first or subsequent set of dynamic config from config source including a dynamic config file.
672 * If you use dynamic config: You must invoke this, or apply_static_and_dynamic(), once before access via
673 * dynamic_values() / all_dynamic_values(). After that, optionally invoke it whenever there is good reason
674 * to believe new dynamic config is available in the dynamic config source(s).
675 *
676 * See also apply_static_and_dynamic(). If not using apply_static_and_dynamic(), *and* this is the initial
677 * apply_dynamic() invoked, then see also apply_dynamic() overload with #allow_invalid_defaults_tag_t tag.
678 *
679 * After the initial apply_dynamic(): It is safe to call apply_dynamic() concurrently with any number of
680 * `[all_]dynamic_values()`. However behavior is undefined if one calls apply_dynamic() concurrently with itself
681 * or apply_static_and_dynamic() (on the same `*this`).
682 *
683 * On failure returns `false`; else returns `true`. In the former case the canonical state remains unchanged,
684 * and any candidate built-up via preceding `commit == false` calls (if any) is discarded. WARNING(s) logged
685 * given failure.
686 *
687 * apply_dynamic() will be tolerant of unknown option names appearing in the config source, though it will
688 * log about them. The reasoning: Dynamic config must grapple with backward- and forward-compatibility.
689 *
690 * For context w/r/t `commit` arg: Please read the section of the class doc header regarding multi-source
691 * updates. Corner case note: Formally: invoking `apply_Y()` immediately after `apply_X(commit = false) == true`,
692 * without reject_candidates() between them, where Y and X differ => `apply_Y()` shall log an INFO message
693 * and invoke reject_candidates() itself; then proceed. In this case `apply_Y()` is apply_dynamic().
694 * Informally we discourage doing this; it is stylistically better to invoke reject_candidates()
695 * explicitly in that scenario which cancels an in-progress update which is unusual though conceivably useful.
696 *
697 * ### Effect of a validator yielding SKIP ###
698 * Identical to apply_static().
699 *
700 * ### Performance ###
701 * dynamic_values() + all_dynamic_values() locking performance is no worse than: lock mutex,
702 * assign #S_N_D_VALUE_SETS `shared_ptr`s, unlock mutex. The rest of the parse/validate/etc. code is outside any such
703 * critical section. See also Performance section of dynamic_values() doc header.
704 *
705 * Outside that locking critical section, apply_dynamic() may be expensive, in that it performs file input and some
706 * internal copying of option value sets and maps, plus detailed logging. It is, however, not typically called
707 * frequently. Just be aware it will block the calling thread, albeit still only for a split second in normal
708 * conditions.
709 *
710 * @tparam Value_set
711 * These must be `S_d_value_set` template param pack args 1, 3, ..., numbering
712 * #S_N_D_VALUE_SETS in order.
713 * @param dynamic_cfg_path
714 * File to read.
715 * @param final_validator_func
716 * See apply_static_and_dynamic().
717 * @param commit
718 * `true` means that this call being successful (returning `true`) shall cause the promotion of
719 * each candidate `Value_set` built-up so far (via this and all preceding successful calls with
720 * `commit == false`) to canonical state (accessed via dynamic_values() or all_dynamic_values());
721 * and all relevant dynamic change listeners are synchronously called for each `Value_set` for which
722 * the cumulative candidate differs from the canonical state.
723 * `false` means that this call being successful shall merely create-and-set (if first such call)
724 * or incrementally update (if 2nd, 3rd, ... such call) each candidate `Value_set`; no dynamic change
725 * listeners are invoked.
726 * @return `true` if and only if successfully parsed config source(s) and validated all settings including
727 * `final_validator_func() != S_FAIL` for *all* (dynamic) config sets; and defaults were also all
728 * individually valid, in the case of initial apply_dynamic(). However, if `true` but `commit == false`,
729 * then the canonical values (accessed via dynamic_values() or all_dynamic_values()) have not been updated.
730 */
731 template<typename... Value_set>
732 bool apply_dynamic(const fs::path& dynamic_cfg_path,
733 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
734 bool commit = true);
735
736 /**
737 * Identical to apply_dynamic() overload without #allow_invalid_defaults_tag_t tag; except that -- applicably only to
738 * the initial apply_dynamic() without a preceding apply_static_and_dynamic() -- skips the stringent
739 * check on individual defaults' validity.
740 *
741 * @see #allow_invalid_defaults_tag_t doc header and/or return-value doc just below.
742 *
743 * @tparam Value_set
744 * See other apply_dynamic().
745 * @param dynamic_cfg_path
746 * See other apply_dynamic().
747 * @param final_validator_func
748 * See other apply_dynamic().
749 * @param commit
750 * See other apply_dynamic().
751 * @return See other apply_dynamic(). However -- assuming apply_static_and_dynamic() was not used, and
752 * this is the initial apply_dynamic() -- the other apply_dynamic() will return `false` if a default is
753 * invalid, even if file `dynamic_cfg_path` explicitly sets it to a valid value. This tagged overload will
754 * not and let parsing continue. If `apply_static_and_dynamic(commit = true) == true` was used, or
755 * if `apply_dynamic(commit = true) == true` has been called before, then the overloads behave
756 * identically: defaults are not checked; it must occur just before the initial dynamic load or never.
757 */
758 template<typename... Value_set>
760 const fs::path& dynamic_cfg_path,
761 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
762 bool commit = true);
763
764 /**
765 * Cancel a not-yet-canonicalized (incomplete) multi-source update, if one is in progress. If one is not
766 * in-progress, INFO-log but otherwise no-op.
767 *
768 * This method only has effect following apply_static(), apply_static_and_dynamic(), or apply_dynamic() that
769 * - returned `true`; and
770 * - had arg `commit == false`.
771 *
772 * That is, if there are pending *candidate* `Value_set`s that have not yet been upgraded to canonical status
773 * via a `commit == true` `apply_*()` call, this call discards them, making it as-if no such call(s) were
774 * made in the first place.
775 *
776 * Informally we do not expect this to be commonly used; typically `apply_*()` will automatically do this
777 * upon encountering an error (in individual option validation or due to Final_validator_outcome::S_FAIL).
778 * However if there is some outside reason to abort an ongoing, so-far-successful multi-source update
779 * this method will similarly do it.
780 */
782
783 /**
784 * Emit a pointer to each permanently set static config value set; the same pointers are emitted throughout
785 * for each of the #S_N_S_VALUE_SETS static config slots. Tip: It should be sufficient to pass around only `const`
786 * refs (from the pointers obtained here) all around the app -- no `Value_set` copying should be needed.
787 *
788 * @tparam Value_set
789 * These must be `S_d_value_set` template param pack args 0, 2, ..., numbering
790 * #S_N_S_VALUE_SETS in order. The compiler should be able to deduce them automatically from
791 * each `value_set_or_null` type; though when passing in null it may be then necessary to cast `nullptr`
792 * (or `0`) to the appropriate pointer type.
793 * @param value_set_or_null
794 * For each element in the param pack:
795 * `*value_set_or_null` is set to point to the immutable static config set in that slot;
796 * or that slot is ignored if `value_set_or_null` is null.
797 */
798 template<typename... Value_set>
799 void all_static_values(const Value_set**... value_set_or_null) const;
800
801 /**
802 * Similar to all_static_values(), but if called from within a validator function passed to apply_static() or
803 * apply_static_and_dynamic(), then for any static value set for which values have been parsed and validated (but not
804 * yet applied) so far, a pointer to the parsed values *candidate* will be emitted instead. If called from elsewhere,
805 * the behavior is equivalent to all_static_values(). A values candidate consists of what has been parsed and
806 * validated (and not skipped via Final_validator_outcome::S_SKIP) for the value set, but has not yet been applied
807 * such that it would be available through all_static_values().
808 *
809 * This could be useful to provide static values to a final validator function for another value set which depends on
810 * them. During the execution of apply_static() or apply_static_and_dynamic(), the validator function for each value
811 * set will be executed before any are canonicalized. This method can be used by a validator function to obtain the
812 * candidate values which have been parsed and validated (but have not yet been canonicalized) for a preceding static
813 * value set.
814 *
815 * @see all_static_values().
816 *
817 * @tparam Value_set
818 * See all_static_values().
819 * @param value_set_or_null
820 * See all_static_values().
821 */
822 template<typename... Value_set>
823 void all_static_values_candidates(const Value_set**... value_set_or_null) const;
824
825 /**
826 * Similar to all_static_values(), but obtains the static config in *one* specified slot as opposed to all of them.
827 *
828 * @tparam Value_set
829 * This must be `S_d_value_set` template param pack args in position `2 * s_value_set_idx`
830 * (so one of [0, 2, ..., `(2 * S_N_S_VALUE_SETS) - 2`]).
831 * You will need to explicitly specify this `tparam`. E.g.:
832 * `const auto cfg_ptr = cfg_mgr.static_values<S_cfg2>(1)`.
833 * @param s_value_set_idx
834 * The static config slot index; so one of [0, 1, ..., #S_N_S_VALUE_SETS).
835 * @return The immutable, permanently set static config value set in the `s_value_set_idx` slot.
836 */
837 template<typename Value_set>
838 const Value_set& static_values(size_t s_value_set_idx) const;
839
840 /**
841 * Similar to static_values(), but if called from within a validator function passed to apply_static() or
842 * apply_static_and_dynamic(), then the parsed values *candidate* will be returned, instead, if values have been
843 * parsed and validated for the static value set but have not yet been applied. Otherwise, the behavior is equivalent
844 * to static_values().
845 *
846 * @see all_static_values_candidates().
847 *
848 * @tparam Value_set
849 * See static_values().
850 * @param s_value_set_idx
851 * See static_values().
852 * @return See above and static_values().
853 */
854 template<typename Value_set>
855 const Value_set& static_values_candidate(size_t s_value_set_idx) const;
856
857 /**
858 * Obtain ref-counted pointers to each currently-canonical set of dynamic config; each pointed-at `struct` is set
859 * permanently; while another call may return a different pointer if config is changed dynamically in the meantime
860 * (for that slot). For each slot: If you require a consistent set of dynamic config over some period of time or over
861 * some code path, save a copy of the returned ref-counted pointer to the `Value_set` and keep accessing values in
862 * that immutable structure through that ref-counted pointer.
863 *
864 * After the initial apply_dynamic(): It is safe to call all_dynamic_values() concurrently with any number of itself
865 * or of dynamic_values() and/or a call to `apply_dynamic(commit = *)`.
866 *
867 * @note Have each `Value_set` derive from util::Shared_ptr_alias_holder, so that it is endowed with
868 * `Ptr` and `Const_ptr` ref-counted pointer type aliases, for your convenience and as required by
869 * Config_manager and Option_set.
870 *
871 * ### Performance ###
872 * dynamic_values() performance is no worse than: lock mutex,
873 * copy #S_N_D_VALUE_SETS `shared_ptr`s, unlock mutex. See also Performance section of
874 * apply_dynamic() doc header.
875 *
876 * @tparam Value_set
877 * These must be `S_d_value_set` template param pack args 1, 3, ..., numbering
878 * #S_N_D_VALUE_SETS in order. Likely you will need to explicitly specify them, as the compiler will
879 * probably not deduce them. E.g.:
880 * `cfg_mgr.all_dynamic_values<D_cfg1, D_cfg2, Null_value_set>(&d1, &d2, 0)`.
881 * @param value_set_or_null
882 * For each element in the param pack:
883 * `*value_set_or_null` is set to point to the immutable dynamic config set currently canonical in that slot;
884 * or that slot is ignored if `value_set_or_null` is null.
885 * Remember: `**value_set_or_null` is immutable; but another call may yield a different
886 * `*value_set_or_null`.
887 */
888 template<typename... Value_set>
889 void all_dynamic_values(typename Value_set::Const_ptr*... value_set_or_null) const;
890
891 /**
892 * Similar to all_dynamic_values() but obtains the dynamic config in *one* specified slot as opposed to all of them.
893 *
894 * @tparam Value_set
895 * This must be `S_d_value_set` template param pack args in position `1 + 2 * d_value_set_idx`
896 * (so one of [1, 3, ..., `(2 * S_N_D_VALUE_SETS) - 1`]).
897 * You will need to explicitly specify this `tparam`. E.g.:
898 * `const auto cfg_ptr = cfg_mgr.dynamic_values<D_cfg2>(1)`.
899 * @param d_value_set_idx
900 * The dynamic config slot index; so one of [0, 1, ..., #S_N_D_VALUE_SETS).
901 * @return Ref-counted pointer to the immutable dynamic config set currently canonical in that slot.
902 * Remember: if `p` returned, then `*p` is immutable; but another dynamic_values() call may return
903 * a different `p2` not equal to `p`.
904 */
905 template<typename Value_set>
906 typename Value_set::Const_ptr dynamic_values(size_t d_value_set_idx) const;
907
908 /**
909 * Saves the given callback; next time `apply_dynamic(commit = true)` or
910 * `apply_static_and_dynamic(commit = true)` detects at least one changed (or initially set) option value
911 * in the specified slot, it will execute this and any other previously registered such callbacks
912 * synchronously. The callbacks will be called after the pointers to be returned by all_dynamic_values() have all
913 * been updated.
914 *
915 * This is not thread-safe against concurrent calls to itself; nor against concurrent
916 * `apply_dynamic(commit = true)` (against `apply_dynamic(commit = false)` is okay),
917 * `apply_static_and_dynamic(commit = true)` (against `apply_static_and_dynamic(commit = false)` is okay), or
918 * unregister_dynamic_change_listener().
919 *
920 * @note The callback may optionally be unregistered by using unregister_dynamic_change_listener(). This *should* be
921 * done before anything that the callback accesses is invalidated. If there is a chance that `apply*_dynamic()`
922 * might be called later, then the callback *must* be unregistered before anything that it accesses is
923 * invalidated, otherwise there could be undefined behavior when the callback accesses something which is
924 * invalid.
925 *
926 * @tparam Value_set
927 * See dynamic_values().
928 * @param d_value_set_idx
929 * See dynamic_values().
930 * @param on_dynamic_change_func_moved
931 * Function to call synchronously from the next `apply*_dynamic()` that detects a change.
932 * @return A handle which can be used to unregister the callback.
933 */
934 template<typename Value_set>
935 On_dynamic_change_func_handle register_dynamic_change_listener(size_t d_value_set_idx,
936 On_dynamic_change_func&& on_dynamic_change_func_moved);
937
938 /**
939 * Remove a previously registered dynamic change callback. See register_dynamic_change_listener().
940 *
941 * This is not thread-safe against concurrent calls to itself; nor against concurrent
942 * `apply_dynamic(commit = true)` (against `apply_dynamic(commit = false)` is okay),
943 * `apply_static_and_dynamic(commit = true)` (against `apply_static_and_dynamic(commit = false)` is okay), or
944 * register_dynamic_change_listener().
945 *
946 * @param handle
947 * The handle which was returned by register_dynamic_change_listener() when the callback was registered.
948 */
949 void unregister_dynamic_change_listener (const On_dynamic_change_func_handle& handle);
950
951 /**
952 * Prints a human-targeted long-form summary of our contents, doubling as a usage message and a dump of current
953 * values where applicable. This is not thread-safe against several non-`const` methods.
954 *
955 * @param os
956 * Stream to which to serialize.
957 */
958 void state_to_ostream(std::ostream& os) const;
959
960 /**
961 * Logs what state_to_ostream() would print. This is not thread-safe against several non-`const` methods.
962 *
963 * @param sev
964 * Severity to use for the log message(s).
965 */
967
968 /**
969 * Prints a human-targeted long-form usage message that includes all options with their descriptions and defaults.
970 * This is thread-safe against all concurrent methods on `*this` and can be invoked anytime after ctor.
971 *
972 * @param os
973 * Stream to which to serialize.
974 */
975 void help_to_ostream(std::ostream& os) const;
976
977 /**
978 * Logs what help_to_ostream() would print.
979 *
980 * @param sev
981 * Severity to use for the log message(s).
982 */
984
985 // Data.
986
987 /// See `nickname` ctor arg.
988 const std::string m_nickname;
989
990private:
991 // Types.
992
993 /// Short-hand for `shared_ptr`-to-`void` type used to store variable-type values in internal containers.
994 using Void_ptr = boost::shared_ptr<void>;
995
996 /**
997 * Useful at least in the context of multi-source (`commit == false`) `apply_*()` methods, this distinguishes
998 * distinct possible public `apply_*()` methods.
999 */
1000 enum class Update_type
1001 {
1002 /// Indicates no `apply_*()` operation.
1003 S_NONE,
1004 /// Indicates apply_static().
1005 S_STATIC,
1006 /// Indicates apply_static_and_dynamic().
1007 S_STATIC_AND_DYNAMIC,
1008 /// Indicates apply_dynamic().
1009 S_DYNAMIC
1010 }; // enum class Update_type
1011
1012 // Methods.
1013
1014 /**
1015 * Helper that obtains the Option_set in the slot `m_s_d_opt_sets[value_set_idx]`, which
1016 * stores an Option_set<Value_set>.
1017 *
1018 * @tparam Value_set
1019 * `S_d_value_set` in slot `value_set_idx`.
1020 * @param value_set_idx
1021 * One of [0, 1, ..., #S_N_VALUE_SETS).
1022 * @return Pointer to the Option_set.
1023 */
1024 template<typename Value_set>
1025 Option_set<Value_set>* opt_set(size_t value_set_idx);
1026
1027 /**
1028 * Helper that obtains the baseline dynamic `Value_set` in the slot `m_d_baseline_value_sets[value_set_idx]`, which
1029 * stores a `Value_set`.
1030 *
1031 * @tparam Value_set
1032 * `S_d_value_set` in dynamic slot `d_value_set_idx`.
1033 * @param d_value_set_idx
1034 * One of [0, 1, ..., #S_N_D_VALUE_SETS).
1035 * @return Pointer to the `Value_set`.
1036 */
1037 template<typename Value_set>
1038 Value_set* d_baseline_value_set(size_t d_value_set_idx);
1039
1040 /**
1041 * `const` overload of the other opt_set() helper.
1042 * @tparam Value_set
1043 * See other opt_set().
1044 * @param value_set_idx
1045 * See other opt_set().
1046 * @return See other opt_set().
1047 */
1048 template<typename Value_set>
1049 const Option_set<Value_set>* opt_set(size_t value_set_idx) const;
1050
1051 /**
1052 * Helper that executes `opt_set->canonicalize_candidate()` or
1053 * `opt_set->reject_candidate()`. Used in meta-programming. Apply when parsing `*opt_set`,
1054 * assuming `commit == true`.
1055 *
1056 * @tparam Value_set
1057 * See opt_set().
1058 * @param opt_set
1059 * An opt_set() result.
1060 * @param canonicalize_else_reject
1061 * Specifies whether to... well, you know.
1062 * @param changed_or_null
1063 * If not null and canonicalizing (not rejecting), `*changed_or_null` is set to whether
1064 * the `canonicalize_candidate()` detected at least one option's value changed from the previous canonical
1065 * state.
1066 */
1067 template<typename Value_set>
1069 bool canonicalize_else_reject, bool* changed_or_null);
1070
1071 /**
1072 * Implements all apply_static() overloads.
1073 *
1074 * @tparam Value_set
1075 * See apply_static().
1076 * @param allow_invalid_defaults
1077 * `true` if and only if #allow_invalid_defaults_tag_t used.
1078 * @param static_cfg_path
1079 * See apply_static().
1080 * @param final_validator_func
1081 * See apply_static().
1082 * @param commit
1083 * See apply_static().
1084 * @return See apply_static().
1085 */
1086 template<typename... Value_set>
1087 bool apply_static_impl(bool allow_invalid_defaults,
1088 const fs::path& static_cfg_path,
1089 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
1090 bool commit);
1091
1092 /**
1093 * Implements all apply_dynamic() overloads.
1094 *
1095 * @tparam Value_set
1096 * See apply_dynamic().
1097 * @param allow_invalid_defaults
1098 * `true` if and only if #allow_invalid_defaults_tag_t used.
1099 * @param dynamic_cfg_path
1100 * See apply_dynamic().
1101 * @param final_validator_func
1102 * See apply_dynamic().
1103 * @param commit
1104 * See apply_static().
1105 * @return See apply_dynamic().
1106 */
1107 template<typename... Value_set>
1108 bool apply_dynamic_impl(bool allow_invalid_defaults,
1109 const fs::path& dynamic_cfg_path,
1110 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
1111 bool commit);
1112
1113 /**
1114 * Implements all apply_static_and_dynamic() overloads.
1115 *
1116 * @param allow_invalid_defaults
1117 * `true` if and only if #allow_invalid_defaults_tag_t used.
1118 * @param cfg_path
1119 * See apply_static_and_dynamic().
1120 * @param final_validator_func
1121 * See apply_static_and_dynamic().
1122 * @param commit
1123 * See apply_static_and_dynamic().
1124 * @return See apply_static_and_dynamic().
1125 */
1126 bool apply_static_and_dynamic_impl(bool allow_invalid_defaults,
1127 const fs::path& cfg_path,
1128 const typename Final_validator_func<S_d_value_set>::Type&... final_validator_func,
1129 bool commit);
1130
1131 /**
1132 * Work-horse helper that parses either *all* static value sets *or* *all* dynamic value sets from the specified
1133 * file into #m_s_d_opt_sets and returns `true` if and only if all have succeeded. (It stops on first failure.)
1134 * The canonicalize/reject step is not performed: only parsing and (if that worked) `final_validator_func()`.
1135 * Recall that `final_validator_func() == S_SKIP`, even though it will cause us to not have changed that `Value_set`'s
1136 * Option_set::values_candidate(), is still considered success.
1137 *
1138 * @tparam Value_set
1139 * See apply_static() or apply_dynamic() depending on `dyn_else_st`.
1140 * @param dyn_else_st
1141 * Basically whether apply_static() or apply_dynamic() was called.
1142 * @param cfg_path
1143 * See apply_static() or apply_dynamic() depending on `dyn_else_st`.
1144 * @param all_opt_names_or_empty
1145 * Empty if *any* option names may be present in file (such as to support dynamic config compatibility);
1146 * otherwise must contain any option names that are allowed in this file (such as for apply_static()).
1147 * @param final_validator_func
1148 * See apply_static() or apply_dynamic() depending on `dyn_else_st`.
1149 * @return See apply_static() or apply_dynamic() depending on `dyn_else_st`.
1150 */
1151 template<typename... Value_set>
1153 (bool dyn_else_st,
1154 const fs::path& cfg_path, const boost::unordered_set<std::string>& all_opt_names_or_empty,
1155 const typename Final_validator_func<Value_set>::Type&... final_validator_func);
1156
1157 /**
1158 * Work-horse helper that parses into one given Option_set in #m_s_d_opt_sets (from opt_set()) from the specified
1159 * file and returns `true` if and only if successful. The canonicalize/reject step is not performed: only
1160 * parsing and (if that worked) `final_validator_func()`.
1161 * Recall that `final_validator_func() == S_SKIP`, even though it will cause us to not have changed that `Value_set`'s
1162 * Option_set::values_candidate(), is still considered success.
1163 *
1164 * If `opt_set->null() == true`, this completely no-ops and returns `true`.
1165 *
1166 * ### Effect of `skip_parsing` ###
1167 * It is a pointer. If null (or if `opt_set->null() == true`), then the behavior is as written.
1168 *
1169 * If not null, then it has the following added effects -- used as of this writing by apply_dynamic():
1170 * - If and only if, at entry to method, `*skip_parsing == true`, then:
1171 * - The contents of the file shall be ignored, and `final_validator_func()` shall not be called;
1172 * instead we act *as-if* the contents were individually valid, but `final_validator_func()` yielded `S_SKIP`.
1173 * - On return from method: `*skip_parsing` shall be set to `true` if and only if:
1174 * - `*skip_parsing = true` at entry to method; or else if
1175 * - values in file were individually valid, but `final_validator_func()` yielded `S_SKIP`.
1176 *
1177 * More in English: To get the desired SKIP behavior, where a SKIPped `Value_set` also skips all subsequent
1178 * (but not preceding) ones, start with `bool skip_parsing = false`, then pass `&skip_parsing` to each apply_impl().
1179 * (The `skip_parsing == nullptr` mode is still provided, in case it's useful for something later; as of this
1180 * writing all the public `apply_*()` APIs have `skip_parsing != nullptr`, but it could conceivably change.)
1181 *
1182 * @tparam Value_set
1183 * See opt_set().
1184 * @param opt_set
1185 * An opt_set() result.
1186 * @param baseline_value_set_or_null
1187 * If null then ignored; else -- just before attempting to parse and apply values from `cfg_path` -- we
1188 * will set `*opt_set` to contain a copy of `Value_set *baseline_value_set_or_null`. This is to ensure
1189 * a consistent state after each dynamic config update as opposed to working incrementally.
1190 * @param cfg_path
1191 * See apply_static_or_dynamic_impl().
1192 * @param all_opt_names_or_empty
1193 * See apply_static_or_dynamic_impl().
1194 * Technically this need not include the ones actually parsed by `*opt_set`; but including them is harmless
1195 * (and may be helpful to the caller for simplicity).
1196 * @param final_validator_func
1197 * See apply_static() or apply_dynamic() or apply_static_and_dynamic().
1198 * @param skip_parsing
1199 * See above. Recall it can be null.
1200 * @return `true` on success; `false` on failure.
1201 */
1202 template<typename Value_set>
1203 bool apply_impl(Option_set<Value_set>* opt_set, const Value_set* baseline_value_set_or_null, const fs::path& cfg_path,
1204 const boost::unordered_set<std::string>& all_opt_names_or_empty,
1205 const typename Final_validator_func<Value_set>::Type& final_validator_func,
1206 bool* skip_parsing);
1207
1208 /**
1209 * Helper for the top of `apply_*()` that guards against a call to `apply_Y()` following
1210 * `apply_X(commit == false) == true` without a reject_candidates() between them. If this is detected, it
1211 * effectively "inserts" the "missing" reject_candidates() call -- plus an INFO message.
1212 *
1213 * Post-condition: #m_multi_src_update_in_progress equals Update_type::S_NONE.
1214 *
1215 * @param this_update_type
1216 * The type (not NONE, or behavior undefined -- assert may trip) of `apply_*()` calling us.
1217 */
1219
1220 /**
1221 * Little helper that, having assumed #m_d_value_sets_mutex is locked, makes a newly allocated copy of the canonical
1222 * dynamic config at the given slot and saves a (new) ref-counted pointer to it in the proper #m_d_value_sets slot.
1223 *
1224 * @tparam Value_set
1225 * See opt_set().
1226 * @param opt_set
1227 * An opt_set() result (dynamic slot).
1228 * @param d_value_set_idx
1229 * One of [0, 1, ..., #S_N_D_VALUE_SETS).
1230 */
1231 template<typename Value_set>
1233
1234 /**
1235 * Invokes the registered listeners for the given dynamic config slot (synchronously).
1236 *
1237 * @param d_value_set_idx
1238 * One of [0,1, ... #S_N_D_VALUE_SETS).
1239 * @param init
1240 * For logging (as of this writing): whether this is due to the initial parsing of this dynamic config slot
1241 * or a subsequent change.
1242 */
1243 void invoke_dynamic_change_listeners(size_t d_value_set_idx, bool init) const;
1244
1245 /**
1246 * Helper of state_to_ostream() for #m_s_d_opt_sets slot `value_set_idx`.
1247 *
1248 * @param value_set_idx
1249 * One of [0, 1, ..., #S_N_VALUE_SETS).
1250 * @param os
1251 * See state_to_ostream().
1252 */
1253 template<typename Value_set>
1254 void state_to_ostream_impl(size_t value_set_idx, std::ostream& os) const;
1255
1256 /**
1257 * Helper of help_to_ostream() for #m_s_d_opt_sets slot `value_set_idx`.
1258 *
1259 * @param value_set_idx
1260 * One of [0, 1, ..., #S_N_VALUE_SETS).
1261 * @param os
1262 * See help_to_ostream().
1263 */
1264 template<typename Value_set>
1265 void help_to_ostream_impl(size_t value_set_idx, std::ostream& os) const;
1266
1267 // Data.
1268
1269 /**
1270 * The static and dynamic value sets, in the same order as the #S_N_VALUE_SETS
1271 * `S_d_value_set` template args, the specific type for each slot being: `Option_set<S_d_value_set>`.
1272 * This is the main data store managed by Config_manager. Each static `S_d_value_set` (the even slots) is the
1273 * entirety of data having to do with that piece of static config. Each dynamic `S_d_value_set` (the odd slots) is
1274 * the *canonical* dynamic config; and a pointer to a *copy* thereof is also maintained in #m_d_value_sets.
1275 *
1276 * The slots, in order, are hence:
1277 * - Index [0]: static slot 0,
1278 * - Index [1]: dynamic slot 0,
1279 * - Index [2]: static slot 1,
1280 * - Index [3]: dynamic slot 1,
1281 * - ...
1282 * - Index [`.size() - 2`]: static slot `S_N_S_VALUE_SETS - 1`.
1283 * - Index [`.size() - 1`]: dynamic slot `S_N_D_VALUE_SETS - 1`.
1284 *
1285 * The actual type stored is `shared_ptr<void>`, with each slot's held pointer actually being an
1286 * `Option_set<S_d_value_set>*`.
1287 * opt_set() obtains the actual `Option_set<>` at a given slot, performing the required casting.
1288 *
1289 * ### Rationale ###
1290 * Why use an array of `shared_ptr<void>`, which requires a `static_cast` anytime we need to actually
1291 * access a config set? The more compile-time/meta-programm-y approach I (ygoldfel) could conceive of was to use
1292 * `tuple<S_d_value_set...>`. That would be quite elegant and lightning-fast (though compilation might be very slow).
1293 * However then, if I understand correctly, that would prevent any kind of runtime iteration through them.
1294 * This might be absolutely fine, if every slot were treated identically, but in the current setup it's
1295 * static, dynamic, static, dynamic, .... Therefore, by moving some processing to runtime instead of compile-time
1296 * we gain flexibility. (Do note my disclaimer in Implementation section in class doc header -- indicating this is
1297 * my first foray into advanced meta-programming like this. So perhaps I lack imagination/experience.)
1298 *
1299 * So then `shared_ptr<void>` actually stores an `S_d_value_set*` -- `S_d_value_set` being a different type for
1300 * each slot in the array. This uses the type erasure pattern of standard C++ smart pointers, wherein because
1301 * `static_cast<void*>(T*)` is allowed for any type `T`, `shared_ptr<void>` deleter will properly `delete` the `T*`,
1302 * as long as the `shared_ptr` ctor is passed a `T*` raw pointer. Do note this is type-safe only if our code
1303 * knows to cast to the proper `T*` when the pointer is accessed.
1304 *
1305 * Why `shared_ptr` -- not `unique_ptr` (which is nominally faster in that there's no ref-counting)? Answer:
1306 * `unique_ptr` is apparently deliberately simple and lacks aliasing and type-erasure constructors.
1307 * `unique_ptr<T>` can only delete a `T` with the default deleter; and `T` being `void` therefore won't compile.
1308 * It is probably possible to hack it with a custom deleter -- but this seemed like overkill, as these handles
1309 * are never used except inside an `array`, so reference counting shouldn't even happen and thus cost any perf
1310 * cycles outside of `*this` ctor and dtor.
1311 *
1312 * An alternative, which is somewhat more type-safe in that it would have resulted in an RTTI exception if the
1313 * wrong type-cast were used on the stored value, was `any`. However `any` is slower, involving `virtual` lookups
1314 * (at least in a naive implementation of `any`).
1315 */
1316 boost::array<Void_ptr, S_N_VALUE_SETS> m_s_d_opt_sets;
1317
1318 /**
1319 * The baseline dynamic value sets, in the same order as #S_N_D_VALUE_SETS *dynamic*
1320 * `S_d_value_set` template args, the specific type for each slot being `S_d_value_set`.
1321 *
1322 * The actual type stored is `shared_ptr<void>`, with each slot's held pointer actually being `S_d_value_set*`.
1323 * The same type erasure technique used in #m_s_d_opt_sets is used here.
1324 * d_baseline_value_set() obtains the actual `S_d_value_set` at a given slot, performing the required casting.
1325 *
1326 * A null pointer is stored if the corresponding Option_set in #m_s_d_opt_sets is `null()` (a placeholder with 0
1327 * settings).
1328 *
1329 * ### Rationale / explanation ###
1330 * Suppose a dynamic `Value_set` is to be parsed from file F potentially repeatedly, as dynamic updates come in.
1331 * Then its Option_set from `m_s_d_opt_sets[]` would `parse_config_file(F)` each time. However, if that is all
1332 * we did, then the updates would stack on top of each other incrementally: if one looks at F's contents at a given
1333 * time, one cannot tell what the `Value_set` in memory would contain upon parsing it; it would depend on any
1334 * preceding updates. That's bad: the resulting memory `Value_set` must be consistent, regardless of what
1335 * F contained in preceding updates. Therefore, a certain *baseline* state for that `Value_set` must be loaded
1336 * into the Option_set just before the Option_set::parse_config_file() call in each update.
1337 *
1338 * This stores each dynamic slot's baseline `Value_set` state to load as just described.
1339 *
1340 * After construction this payload is just the default-cted `Value_set()`. If the user chooses to execute
1341 * a one-time apply_static_and_dynamic(), then that payload is replaced by the state after having parsed that
1342 * baseline state. Note, for context, that apply_static_and_dynamic() would be presumably loading *not*
1343 * from file F (which can change repeatedly, as dynamic updates come in) but some other file B, typically storing
1344 * both static value sets' contents as well as the baseline dynamic value sets'.
1345 */
1346 boost::array<Void_ptr, S_N_D_VALUE_SETS> m_d_baseline_value_sets;
1347
1348 /**
1349 * The dynamic config ref-counted handles returned by all_dynamic_values(). Each one is a ref-counted pointer
1350 * to an immutable *copy* of the canonical `S_d_value_set` in that *dynamic* slot of #m_s_d_opt_sets
1351 * (namely `m_s_d_opt_sets[2 * d_idx + 1]`, where `d_idx` is in [0, 1, ..., #S_N_D_VALUE_SETS)).
1352 *
1353 * The actual type stored is `shared_ptr<void>`, with each slot's held pointer actually being
1354 * `S_d_value_set::Const_ptr*`. The same type erasure technique used in #m_s_d_opt_sets is used here.
1355 *
1356 * Protected by #m_d_value_sets_mutex.
1357 *
1358 * apply_dynamic() and apply_static_and_dynamic() set these; all_dynamic_values() and dynamic_values() get them.
1359 *
1360 * ### Rationale ###
1361 * Why is this necessary? Why not just expose a pointer to immutable `S_d_value_set` directly inside
1362 * `m_s_d_opt_sets[d_idx]` as we do (via static_values() and all_static_values()) for the static config? Answer:
1363 * Because with dynamic config it is important that the user be able to retain (if even for a short time, perhaps to
1364 * complete some operation -- *during* which `*this` might process a dynamic update via apply_dynamic()) an immutable
1365 * `S_d_value_set`. Since by definition that's not possible with dynamic config updates, we must create a copy. At
1366 * that point it becomes natural to wrap it in a `shared_ptr`, so that only *one* copy is necessary in the entire
1367 * application -- and it'll disappear once (1) the canonical config has been updated, replacing `m_d_value_sets[]`,
1368 * and (2) all user code has dropped the remaining refs to it.
1369 *
1370 * The ref-counted-pointer-to-dynamic-config is a common pattern. The only thing we add that might not be obvious
1371 * is having the canonical copy be separate from the one we expose via the pointer publicly. The latter is
1372 * the natural consequence of the fact that the `Value_set` managed by `Option_set<Value_set>` lives inside
1373 * the `Option_set` and is not wrapped by any `shared_ptr`. However Option_set::mutable_values_copy() does provide
1374 * easy access to a `shared_ptr`-wrapped copy thereof... so we use it. The perf cost is that of a value-set
1375 * copy at apply_dynamic() time which is negligible in the grand scheme of things.
1376 */
1377 boost::array<Void_ptr, S_N_D_VALUE_SETS> m_d_value_sets;
1378
1379 /// Mutex protecting #m_d_value_sets.
1381
1382 /**
1383 * List of callbacks to execute after #m_d_value_sets members (the pointers) are next assigned: one per
1384 * each of the #S_N_D_VALUE_SETS dynamic config slots. Note we do *not*
1385 * assign a given slot's #m_d_value_sets pointer, unless at least one option has changed within the associated
1386 * canonical value set in #m_s_d_opt_sets.
1387 *
1388 * ### Rationale ###
1389 * A `list<>` is used here so that iterators to callbacks can be stored in `On_dynamic_change_func_handle`s without
1390 * the possibility of them being invalidated by insertions to or removals from the list (which would be the case if
1391 * `vector<>` were used).
1392 */
1393 boost::array<std::list<On_dynamic_change_func>, S_N_D_VALUE_SETS> m_on_dynamic_change_funcs;
1394
1395 /// Starts `false`; set to `true` permanently on successful apply_static_and_dynamic() or apply_dynamic().
1397
1398 /**
1399 * In short, truthy if and only if a `commit == false` update is currently in progress,
1400 * meaning there's an uncommitted *candidate* `Value_set` at the moment. More precisely:
1401 * - If the last-invoked `apply_*()` method (1) returned `false`, or (2) returned `true` but with arg
1402 * `commit == true`, or (3) has never been called, or (4) was followed at any point
1403 * by reject_candidates(), then this equals Update_type::S_NONE. I.e., no multi-source update is
1404 * currently in progress. Otherwise:
1405 * - This indicates which `apply_*(commit = true) == true` method (multi-source update type) it was.
1406 * To the extent the exact type is irrelevant, its being not NONE indicates a multi-source update
1407 * is in progress.
1408 *
1409 * Inside an `apply_*()` method, this is updated to its new value after parsing into #m_s_d_opt_sets. Thus
1410 * during the bulk of such a method's processing we can easily determine whether this is step 1 of a
1411 * (potentially multi-source) update -- rather than step 2+ -- as this will equal NONE in the former
1412 * case only.
1413 *
1414 * When this is not-NONE, an `apply_*()` impl shall skip a couple of steps it would otherwise perform:
1415 * - Individually-validating the default `Value_set()` values: Skip, as the first `apply_*()` in the sequence
1416 * would have already done it. So it's a waste of compute/entropy.
1417 * - `apply_dynamic()` applying #m_d_baseline_value_sets onto to #m_s_d_opt_sets: Skip, as the first `apply_*()` in
1418 * the sequence would have already done it. So doing it again would be not only redundant but also destructive,
1419 * overwriting any incremental changes made in preceding 1+ `apply_*()` calls.
1420 *
1421 * It would have been enough for it to be a `bool` for those tests. It is an `enum` to be able to detect
1422 * calling `apply_X()` after `apply_Y(commit = false) == true` without reject_candidates() in-between.
1423 */
1425}; // class Config_manager
1426
1427/// Opaque handle for managing a dynamic config change callback.
1428template<typename... S_d_value_set>
1430{
1431 /**
1432 * Opaque handle detail.
1433 * @internal
1434 * The dynamic config slot index for the value set which is associated with the callback. This is used as an index
1435 * into `m_on_dynamic_change_funcs` to specify the list of callbacks which contains the callback.
1436 */
1438
1439 /**
1440 * Opaque handle detail.
1441 * @internal
1442 * Iterator to the callback within the associated callback list.
1443 */
1444 std::list<On_dynamic_change_func>::const_iterator m_pos;
1445};
1446
1447// Free functions: in *_fwd.hpp.
1448
1449// However the following refer to inner type(s) and hence must be declared here and not _fwd.hpp.
1450
1451/**
1452 * Returns a value usable as `final_validator_func` arg to Config_manager::apply_static() and others -- for a
1453 * Null_value_set value set.
1454 *
1455 * @return See above.
1456 */
1458
1459/**
1460 * Returns a value usable as `declare_opts_func_moved` Config_manager ctor arg for a Null_value_set value set.
1461 * Naturally it does nothing.
1462 *
1463 * @return See above.
1464 */
1466
1467// Template implementations.
1468
1469template<typename... S_d_value_set>
1471 (log::Logger* logger_ptr, util::String_view nickname,
1472 typename Option_set<S_d_value_set>::Declare_options_func&&... declare_opts_func_moved) :
1473 log::Log_context(logger_ptr, Flow_log_component::S_CFG),
1474 m_nickname(nickname),
1475 m_dynamic_values_set(false), // Becomes `true` when apply*_dynamic() succeeds once.
1476 m_multi_src_update_in_progress(Update_type::S_NONE)
1477{
1479
1480 /* @todo OK; this is really weird; I wanted to make this a static_assert() (and put it near where this constexpr
1481 * is initialized), but it is then triggered with gcc claiming Config_manager<> (with no tparams!) is
1482 * compiled -- bizarrely due to a simple FLOW_LOG_INFO() line a few hundred lines below this. I find this
1483 * inexplicable; almost seems like a bug, but maybe not. Look into it. -ygoldfel */
1484 assert((S_N_VALUE_SETS != 0)
1485 && "Must have at least 2 template args to Config_manager; otherwise what are you configuring?");
1486
1487 // (See class doc header Implementation section for explanation of fold-expression (..., ) trick.)
1488
1489 /* Set each m_s_d_opt_sets[] to store the Option_set<S_d_value_set>, for each S_d_value_set (class tparam-pack).
1490 * Recall that m_s_d_opt_sets[] stores them in order static, dynamic, static, dynamic, ....
1491 *
1492 * Set each m_d_baseline_value_sets[] to store a default-valued S_d_value_set (for dynamic sets only).
1493 * These may get overwritten optionally (because apply_static_and_dynamic() is optional) once, in
1494 * apply_static_and_dynamic(). For explanation what this is for see m_d_baseline_value_sets doc header.
1495 *
1496 * Initialize each m_d_value_sets[] as well. */
1497 bool dyn_else_st = false;
1498 size_t value_set_idx = 0;
1499 size_t d_value_set_idx = 0;
1500 (
1501 ...,
1502 (
1503 // I (ygoldfel) tried make_shared() here, but it was too much for that gcc. The perf impact is negligible anyway.
1504 m_s_d_opt_sets[value_set_idx]
1505 = Void_ptr
1506 (new Option_set<S_d_value_set> // <-- Parameter pack expansion kernel here.
1507 (get_logger(),
1508 ostream_op_string(m_nickname, dyn_else_st ? "/dynamic" : "/static",
1509 value_set_idx / 2), // 0, 0, 1, 1, ....
1510 std::move(declare_opts_func_moved))), // (And here.)
1511
1512 /* As noted above, fill a m_d_baseline_value_sets[] but for dynamic (non-null()) sets only.
1513 * Same deal for initializing m_d_value_sets.
1514 * (This is `if (dyn_else_st) { ... }` in expression form.) */
1515 dyn_else_st
1516 // * (This is `if (!(...).null()) { ... }` in expression form.)
1517 && (opt_set<S_d_value_set>(value_set_idx)->null() // (And here.)
1518 || (m_d_baseline_value_sets[d_value_set_idx]
1519 = Void_ptr(new S_d_value_set), // (And here.)
1520 true),
1521 m_d_value_sets[d_value_set_idx]
1522 = Void_ptr(new typename S_d_value_set::Const_ptr), // (And here.)
1523 ++d_value_set_idx,
1524 true),
1525
1526 ++value_set_idx,
1527 dyn_else_st = !dyn_else_st
1528 )
1529 );
1530
1531 FLOW_LOG_TRACE("Config_manager [" << *this << "]: Ready; "
1532 "managing [" << S_N_S_VALUE_SETS << "] dynamic+static option set pairs (some may be null/unused).");
1533} // Config_manager::Config_manager()
1534
1535template<typename... S_d_value_set>
1536template<typename Value_set>
1539{
1540 // Fish out the value stuffed into the ptr<void> at the given slot by the ctor.
1541
1542 assert(value_set_idx < S_N_VALUE_SETS);
1543 return static_cast<Option_set<Value_set>*> // Attn: void* -> T* cast; no RTTI to check our type safety.
1544 (m_s_d_opt_sets[value_set_idx].get());
1545}
1546
1547template<typename... S_d_value_set>
1548template<typename Value_set>
1549Value_set*
1551{
1552 // Fish out the value stuffed into the ptr<void> at the given slot by the ctor.
1553
1554 assert(d_value_set_idx < S_N_D_VALUE_SETS);
1555 return static_cast<Value_set*> // Attn: void* -> T* cast; no RTTI to check our type safety.
1556 (m_d_baseline_value_sets[d_value_set_idx].get());
1557}
1558
1559template<typename... S_d_value_set>
1560template<typename Value_set>
1563{
1564 return const_cast<Config_manager<S_d_value_set...>*>(this)->opt_set<Value_set>(value_set_idx);
1565}
1566
1567template<typename... S_d_value_set>
1568template<typename Value_set>
1570 (Option_set<Value_set>* opt_set, bool canonicalize_else_reject, bool* changed_or_null)
1571{
1572 if (opt_set->null())
1573 {
1574 // If null() then *opt_set wasn't even parse*()d (see apply_impl()), so certainly it hasn't changed.
1575 changed_or_null
1576 && (*changed_or_null = false);
1577 }
1578 else
1579 {
1580 canonicalize_else_reject ? opt_set->canonicalize_candidate(changed_or_null)
1581 : opt_set->reject_candidate();
1582 }
1583}
1584
1585template<typename... S_d_value_set>
1586template<typename Value_set>
1587typename Config_manager<S_d_value_set...>::On_dynamic_change_func_handle
1589 (size_t d_value_set_idx, On_dynamic_change_func&& on_dynamic_change_func_moved)
1590{
1591 FLOW_LOG_INFO("Config_manager [" << *this << "]: Adding dynamic-change listener for dynamic value set "
1592 "[" << d_value_set_idx << "].");
1593
1594 assert((d_value_set_idx < S_N_D_VALUE_SETS) && "Invalid dynamic option set index.");
1595 assert((!opt_set<Value_set>((d_value_set_idx * 2) + 1)->null())
1596 && "Do not register dynamic change listener for Null_value_set or otherwise null value set.");
1597
1598 // Not thread-safe (as advertised) against apply*_dynamic() or unregister_dynamic_change_listener().
1599 m_on_dynamic_change_funcs[d_value_set_idx].emplace_back(std::move(on_dynamic_change_func_moved));
1600 const auto& callback_pos = std::prev(m_on_dynamic_change_funcs[d_value_set_idx].end());
1601
1602 FLOW_LOG_INFO("Config_manager [" << *this << "]: Added dynamic-change listener [@" << &(*callback_pos) <<
1603 "] for dynamic value set [" << d_value_set_idx << "].");
1604
1605 return On_dynamic_change_func_handle{ d_value_set_idx, callback_pos };
1606}
1607
1608template<typename... S_d_value_set>
1610{
1611 // Not thread-safe (as advertised) against apply*_dynamic() or register_dynamic_change_listener().
1612
1613 auto& on_dynamic_change_funcs = m_on_dynamic_change_funcs[handle.m_d_value_set_idx];
1614 on_dynamic_change_funcs.erase(handle.m_pos);
1615
1616 FLOW_LOG_INFO("Config_manager [" << *this << "]: Removed dynamic-change listener [@" << &(*handle.m_pos) <<
1617 "] for dynamic value set [" << handle.m_d_value_set_idx << "].");
1618}
1619
1620template<typename... S_d_value_set>
1621template<typename... Value_set>
1623 (const fs::path& static_cfg_path,
1624 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
1625 bool commit)
1626{
1627 return apply_static_impl<Value_set...>(false, static_cfg_path, final_validator_func..., commit);
1628}
1629
1630template<typename... S_d_value_set>
1631template<typename... Value_set>
1634 const fs::path& static_cfg_path,
1635 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
1636 bool commit)
1637{
1638 return apply_static_impl<Value_set...>(true, static_cfg_path, final_validator_func..., commit);
1639}
1640
1641template<typename... S_d_value_set>
1642template<typename... Value_set>
1644 (bool allow_invalid_defaults,
1645 const fs::path& static_cfg_path,
1646 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
1647 bool commit)
1648{
1649 using boost::unordered_set;
1650 using std::string;
1651 using std::declval;
1652 using std::is_same_v;
1653
1654 FLOW_LOG_INFO("Config_manager [" << *this << "]: Request to apply static config file [" << static_cfg_path << "]. "
1655 "Invalid defaults allowed if explicitly set in file? = [" << allow_invalid_defaults << "]. "
1656 "This application (if successful) shall end with canonicalizing values? = [" << commit << "]; "
1657 "multi-source update already in progress (0=none) = [" << int(m_multi_src_update_in_progress) << "].");
1658
1659 reject_candidates_if_update_type_changed(Update_type::S_STATIC);
1660
1661 /* Even if they're all .null(), they must give some validator functions (they'll be ignored and should just
1662 * be assigned null_final_validator_func() a/k/a Final_validator_func<Null_value_set>::null() anyway,
1663 * though we don't enforce it). */
1664 static_assert(sizeof...(Value_set) == S_N_S_VALUE_SETS,
1665 "You must supply N/2 `final_validator_func`s, where N is # of S_d_value_set params. "
1666 "Use null_final_validator_func() for any Null_value_set.");
1667
1668 /* (See class doc header Implementation section for explanation of fold-expression (..., ) trick.)
1669 *
1670 * Note, though, that (... && expr) (of, say, length 4) evaluates to (((expr1 && expr2) && expr3) && expr4);
1671 * which means it'll run through the `expr...i...`s until one evaluates to false; then it stops and evaluates
1672 * no more past that. */
1673
1674 size_t value_set_idx;
1675 bool success = true;
1676
1677 /* Optionally scan the defaults themselves for individual-option-validator validity if so requested.
1678 * However no need to repeat this, if a multi-source update was already in progress. */
1679 if ((!allow_invalid_defaults) && (m_multi_src_update_in_progress == Update_type::S_NONE))
1680 {
1681 value_set_idx = 0;
1682 [[maybe_unused]] const auto dummy = // Defeats false gcc warning due to trailing `success` when N_S_VALUE_SETS is 1.
1683 (
1684 ...
1685 &&
1686 (
1687 opt_set<Value_set>(value_set_idx) // Param pack expansion kernel here.
1688 ->validate_values(&success), // Set `success`...
1689 value_set_idx += 2, // (Skip dynamic ones.)
1690 success // ...and if it's false, the subsequent option sets shall be skipped.
1691 )
1692 );
1693
1694 if (!success) // It would have logged the exact problem, but we still want to clearly say what happened, high-level:
1695 {
1696 FLOW_LOG_WARNING("Config_manager [" << *this << "]: "
1697 "Request to apply static config file [" << static_cfg_path << "]: "
1698 "Will not parse file, because stringent check "
1699 "of defaults detected at least 1 individually-invalid default value. Either correct "
1700 "the default or use apply_static(allow_invalid_defaults_tag_t) overload.");
1701 }
1702 } // if (!allow_invalid_defaults)
1703
1704 // First collect all valid static option names as required by apply_static_or_dynamic_impl().
1705 unordered_set<string> all_opt_names; // The result.
1706 unordered_set<string> const * this_opt_names; // Used for each static slot.
1707 // Ensure Option_set<>::option_names() returns ref-to-const, so this_opt_names can safely point to it.
1708 static_assert(is_same_v<decltype(declval<Option_set<Null_value_set>>().option_names()),
1709 decltype(*this_opt_names)>,
1710 "Option_set<>::option_names() must return ref-to-const. Bug?");
1711 value_set_idx = 0;
1712 if (success) // Skip all this if failed above.
1713 {
1714 (
1715 ...,
1716 (
1717 this_opt_names = &(opt_set<Value_set> // Param pack expansion kernel here.
1718 (value_set_idx)->option_names()),
1719 all_opt_names.insert(this_opt_names->begin(), this_opt_names->end()),
1720 value_set_idx += 2 // Skip dynamic ones.
1721 )
1722 );
1723
1724 // Next do the actual parsing of each static Option_set. Stop at first failure. Return `true` iff total success.
1725 success = apply_static_or_dynamic_impl<Value_set...>
1726 (false, // false => static.
1727 static_cfg_path, all_opt_names, final_validator_func...);
1728 } // if (success) (but it may have become false inside)
1729
1730 if ((!commit) && success)
1731 {
1732 m_multi_src_update_in_progress = Update_type::S_STATIC; // (Was either NONE or already this.)
1733 FLOW_LOG_INFO("Config_manager [" << *this << "]: Multi-source update has successfully begun or continued. "
1734 "Expecting more sources before we can canonicalize candidate(s).");
1735 return true;
1736 }
1737 // else if (commit || (!success)):
1738
1739 /* !success => reject, regardless of commit (a failure aborts a multi-source update too).
1740 * commit => accept or reject depending on `success`.
1741 *
1742 * So finally either canonicalize or reject each parse attempt depending on overall `success` (static only).
1743 * For details on what that means see Option_set<> docs. Basically it just finalizes the parsing by updating
1744 * the canonically stored Value_set inside the Option_set<Value_set>. Until then it's stored to the side as the
1745 * *candidate*. */
1746 value_set_idx = 0;
1747 (
1748 ...,
1749 (
1750 option_set_canonicalize_or_reject(opt_set<Value_set>(value_set_idx), // Param pack expansion kernel here.
1751 success, 0),
1752 value_set_idx += 2 // Skip dynamic ones.
1753 )
1754 );
1755
1756 if (m_multi_src_update_in_progress != Update_type::S_NONE)
1757 {
1758 FLOW_LOG_INFO("Config_manager [" << *this << "]: Multi-source update is now finished.");
1759 m_multi_src_update_in_progress = Update_type::S_NONE;
1760 }
1761 // else { It was NONE and remains so, either due to failure or being a vanilla (single-source) update or both. }
1762
1763 FLOW_LOG_INFO("Config_manager [" << *this << "]: State that was just made canonical follows.");
1764 log_state(); // Note this will auto-skip dynamic sets which should all be Null_state and hence .null().
1765
1766 return success;
1767} // Config_manager::apply_static_impl()
1768
1769template<typename... S_d_value_set>
1770template<typename... Value_set>
1772 (bool dyn_else_st, const fs::path& cfg_path, const boost::unordered_set<std::string>& all_opt_names_or_empty,
1773 const typename Final_validator_func<Value_set>::Type&... final_validator_func)
1774{
1775 size_t s_d_value_set_idx = 0;
1776 size_t value_set_idx = dyn_else_st ? 1 : 0;
1777
1778 /* Flag for the skipping of parsing and validation. Becomes true and stays true once one of the
1779 * final_validator_func()s yields S_SKIP. */
1780 bool skip_parsing = false;
1781
1782 bool success;
1783 return
1784 (
1785 ...
1786 &&
1787 (
1788 /* 2nd apply_impl() arg: If we're called from apply_dynamic(), supply the pre-parse baseline state, but only
1789 * if this isn't the 2nd/3rd/... apply_dynamic() of a multi-source (commit=false) sequence.
1790 * (If it's a null Option_set, then it'll be ignored by the no-op apply_impl().
1791 * Pass null.)
1792 * If we're called from apply_static(), it only happens once and is not relevant.
1793 * Pass null.
1794 * If it is the 2nd/3rd/... apply_dynamic() of a multi-source (commit=false) sequence, the pre-parse
1795 * baseline state has already been applied; and we don't want to blow away the incrementally built-up candidate
1796 * stored inside Value_set opt_set<>().
1797 * Pass null.
1798 * We are never called from apply_static_and_dynamic() which, itself,
1799 * sets that baseline state for later use in apply_dynamic() and hence here. */
1800 success = apply_impl(opt_set<Value_set> // Param pack expansion kernel here.
1801 (value_set_idx), // 1, 3, 5, ... -or- 0, 2, 4, ....
1802
1803 ((m_multi_src_update_in_progress == Update_type::S_NONE)
1804 && dyn_else_st && (!opt_set<Value_set>(value_set_idx)->null()))
1805 ? d_baseline_value_set<Value_set> // (And here.)
1806 (s_d_value_set_idx)
1807 : static_cast<const Value_set*>(0), // (And here.)
1808
1809 cfg_path, all_opt_names_or_empty,
1810 final_validator_func, // (And here.)
1811 &skip_parsing),
1812 ++s_d_value_set_idx,
1813 value_set_idx += 2,
1814 success // Stop apply_impl()ing on first failure (the `&&` will evaluate to false early).
1815 )
1816 );
1817} // Config_manager::apply_static_or_dynamic_impl()
1818
1819template<typename... S_d_value_set>
1820template<typename Value_set>
1822 (Option_set<Value_set>* opt_set,
1823 const Value_set* baseline_value_set_or_null,
1824 const fs::path& cfg_path,
1825 const boost::unordered_set<std::string>& all_opt_names_or_empty,
1826 const typename Final_validator_func<Value_set>::Type& final_validator_func,
1827 bool* skip_parsing)
1828{
1829 assert(opt_set);
1830 auto& opts = *opt_set;
1831
1832 if (opts.null())
1833 {
1834 return true; // By the way notice, as advertised, final_validator_func is ignored.
1835 }
1836 // else
1837
1838 /* For some sets (namely dynamic ones, at most) we need to pre-apply the full set of baseline values.
1839 * This is explained in m_d_baseline_value_sets doc header; but basically it's so that successive dynamic updates
1840 * always apply the change in the file on top of a consistent state. Otherwise it's impossible to know, just by
1841 * looking at the dynamic config file, what the resulting state will be in memory -- incremental is bad in this
1842 * context. */
1843 if (baseline_value_set_or_null)
1844 {
1845 opts.parse_direct_values(*baseline_value_set_or_null);
1846 }
1847
1848 /* We allow final_validator_func()==SKIP, which means the validator decided the candidate Value_set is not
1849 * erroneous but has something there that means this particular apply_impl() shall have no effect on the candidate
1850 * after all. To make this work we must memorize the pre-parse state; as Option_set doesn't know about
1851 * final-validation or skipping and will have updated .values_candidate() (otherwise) irreversibly. We, however,
1852 * can simply undo it via .parse_direct_values() or .reject_candidate().
1853 *
1854 * Subtlety: If, in this possibly-multi-source update, this is the 1st source, then opt_set->values_candidate() may be
1855 * null (*opt_set is not in PARSING state but CANONICAL still). One option is to do what we do: save the canonical
1856 * value-set instead; then if rewinding below, it'll stay in PARSING state but have the unchanged canonical values.
1857 * Another option is to, instead, save nothing and when rewinding below in that case
1858 * reject_candidate() and thus bring *opt_set to CANONICAL state again. That may seem a bit more complex but more
1859 * elegant, but it leads to a subtle bug: If the possibly-multi-source update's final-validators *all* return SKIP --
1860 * which is arguably unusual but certainly allowed -- then apply_*() near its end will happily attempt to
1861 * opt_set->canonicalize_candidate() -- which will fail (possibly assert-trip) due to being invoked in
1862 * already-CANONICAL state. So our choices are to go with the just-save-values() route, or to -- elsewhere
1863 * in apply_*() themselves -- guard ->canonicalize_candidate() with a check for the everything-SKIPped situation.
1864 * Both are okay, but the former approach is simpler, avoiding at least a couple of if-branches in a couple of
1865 * places. It involves potentially 2 extra copy-ops of a Value_set; but so what? In the grand scheme the perf
1866 * impact is negligible. */
1867 const auto candidate_or_null = opt_set->values_candidate();
1868 const auto pre_parse_candidate = candidate_or_null ? *candidate_or_null : opt_set->values();
1869
1870 /* Ready to parse... but act as-if parse was fine but yielded SKIP, if told we're already in skipping mode
1871 * (from previous `Value_set`s, probably, though formally by contract this method doesn't care why). */
1872 if (skip_parsing && (*skip_parsing))
1873 {
1874 FLOW_LOG_INFO("Config_manager [" << *this << "]: "
1875 "Parsing, individual-option validation, and final-validation are being skipped "
1876 "for this value set, as requested by the caller. This file's values will not be applied. "
1877 "This has happened because this value set is successive to a value set whose "
1878 "final-validation determined that it shall not be applied. This is not an error.");
1879 opts.parse_direct_values(pre_parse_candidate);
1880 return true;
1881 }
1882 // else
1883
1884 /* We allow unknown (to this Option_set<Value_set> `opts`) options in the file during the initial parse -- but
1885 * only the ones that are themselves options but in other Option_sets we're also parsing (since non-hard-coded options
1886 * are not being parsed in this file, no other options should be present -- forward-compatibility, etc., not a
1887 * factor with a hard-coded file). Hence simply pass in all option names (including this one's, but they'll
1888 * just be parsed as belonging to this actual set and hence not be relevant to the unknown-option-name check).
1889 *
1890 * If it's not an initial parse then all_opt_names_or_empty will be empty, meaning simply allow all unknown options;
1891 * due to forward/backward-compatibility we just allow it and log such things but ignore them otherwise. */
1892 bool ok;
1893 opts.parse_config_file(cfg_path, true, &ok, all_opt_names_or_empty);
1894
1895 /* parse_config_file() specifically promises not to individually-validate values not in the actual file.
1896 * So before returning success (likely causing to finalize values via opts.canonicalize_candidate()) ensure
1897 * any invalid defaults don't "fall through." This is unnecessary if defaults were scanned individually
1898 * due to allow_invalid_defaults==false; but in that case it is harmless (as of this writing won't even log). */
1899 if (ok)
1900 {
1901 opts.validate_values_candidate(&ok);
1902 if (!ok) // It would have logged the exact problem, but we still want to clearly say what happened, high-level:
1903 {
1904 FLOW_LOG_WARNING("Config_manager [" << *this << "]: "
1905 "Request to apply config file [" << cfg_path << "] for a particular value set: "
1906 "Individual values present in file were OK, but at least 1 default value (see above) "
1907 "was invalid. Either correct the default or ensure it is properly overridden in file.");
1908 }
1909 } // if (ok) (but it may have become false inside)
1910
1911 if (ok)
1912 {
1913 // Lastly, as promised, run their inter-option validation (which can also specify that we skip).
1914 const auto final_validator_outcome = final_validator_func(*opts.values_candidate());
1915 switch (final_validator_outcome)
1916 {
1918 break; // Cool then.
1920 ok = false; // Uncool. It logged.
1921 break;
1922 case Final_validator_outcome::S_SKIP: // Cool-ish.
1923 FLOW_LOG_INFO("Config_manager [" << *this << "]: "
1924 "Request to apply config file [" << cfg_path << "] for a particular value set: "
1925 "Individual values present in file were OK, but the final-validator logic specified "
1926 "that this file's values not be applied (to skip the file after all). "
1927 "Rewinding the candidate value-set to pre-parse state. This is not an error.");
1928 opts.parse_direct_values(pre_parse_candidate);
1929 if (skip_parsing)
1930 {
1931 assert((!*skip_parsing) && "We should have `return`ed already.");
1932 *skip_parsing = true;
1933 }
1934 } // switch (final_validator_outcome). Compiler should warn if we failed to handle an enum value.
1935 } // if (ok) (but it may have become false inside)
1936
1937 return ok;
1938} // Config_manager::apply_impl()
1939
1940template<typename... S_d_value_set>
1942 (const fs::path& cfg_path,
1943 const typename Final_validator_func<S_d_value_set>::Type&... final_validator_func,
1944 bool commit)
1945{
1946 return apply_static_and_dynamic_impl(false, cfg_path, final_validator_func..., commit);
1947}
1948
1949template<typename... S_d_value_set>
1952 const fs::path& cfg_path,
1953 const typename Final_validator_func<S_d_value_set>::Type&... final_validator_func,
1954 bool commit)
1955{
1956 return apply_static_and_dynamic_impl(true, cfg_path, final_validator_func..., commit);
1957}
1958
1959template<typename... S_d_value_set>
1961 (bool allow_invalid_defaults,
1962 const fs::path& cfg_path,
1963 const typename Final_validator_func<S_d_value_set>::Type&... final_validator_func,
1964 bool commit)
1965{
1966 using util::Lock_guard;
1967 using boost::unordered_set;
1968 using std::string;
1969
1970 FLOW_LOG_INFO("Config_manager [" << *this << "]: Request to apply config file [" << cfg_path << "] as both "
1971 "static config and *baseline* dynamic config. Parsing in that order for each pair. "
1972 "Invalid defaults allowed if explicitly set in file? = [" << allow_invalid_defaults << "]. "
1973 "This application (if successful) shall end with canonicalizing values? = [" << commit << "]; "
1974 "multi-source update already in progress (0=none) = [" << int(m_multi_src_update_in_progress) << "].");
1975
1976 reject_candidates_if_update_type_changed(Update_type::S_STATIC_AND_DYNAMIC);
1977
1978 /* (See class doc header Implementation section for explanation of fold-expression (..., ) trick.)
1979 *
1980 * Note, though, that (... && expr) (of, say, length 4) evaluates to (((expr1 && expr2) && expr3) && expr4);
1981 * which means it'll run through the `expr...i...`s until one evaluates to false; then it stops and evaluates
1982 * no more past that. */
1983
1984 size_t value_set_idx;
1985 bool success = true;
1986
1987 /* Optionally scan the defaults themselves for individual-option-validator validity if so requested.
1988 * However no need to repeat this, if a multi-source update was already in progress. */
1989 if ((!allow_invalid_defaults) && (m_multi_src_update_in_progress == Update_type::S_NONE))
1990 {
1991 value_set_idx = 0;
1992 (
1993 ...
1994 &&
1995 (
1996 opt_set<S_d_value_set>(value_set_idx) // Param pack expansion kernel here.
1997 ->validate_values(&success), // Set `success`...
1998 ++value_set_idx,
1999 success // ...and if it's false, the subsequent option sets shall be skipped.
2000 )
2001 );
2002
2003 if (!success) // It would have logged the exact problem, but we still want to clearly say what happened, high-level:
2004 {
2005 FLOW_LOG_WARNING("Config_manager [" << *this << "]: Request to apply config file [" << cfg_path << "] as both "
2006 "static config and *baseline* dynamic config: Will not parse file, because stringent check "
2007 "of defaults detected at least 1 individually-invalid default value. Either correct "
2008 "the default or use apply_static_and_dynamic(allow_invalid_defaults_tag_t) overload.");
2009 }
2010 } // if ((!allow_invalid_defaults) && (!m_multi_src_update_in_progress))
2011
2012 // First collect all valid static *and* dynamic option names as required by apply_impl().
2013 unordered_set<string> all_opt_names;
2014 unordered_set<string> this_opt_names;
2015 value_set_idx = 0;
2016 if (success) // Skip all this if failed above.
2017 {
2018 /* Flag for the skipping of parsing and validation. Becomes true and stays true once one of the
2019 * final_validator_func()s yields S_SKIP. */
2020 bool skip_parsing = false;
2021
2022 (
2023 ...,
2024 (
2025 this_opt_names = opt_set<S_d_value_set>(value_set_idx)->option_names(), // Param pack expansion kernel here.
2026 all_opt_names.insert(this_opt_names.begin(), this_opt_names.end()),
2027 ++value_set_idx
2028 )
2029 );
2030
2031 // Next do the actual parsing of each static *and* dynamic Option_set.
2032 value_set_idx = 0;
2033 (
2034 ...
2035 &&
2036 (
2037 success = apply_impl(opt_set<S_d_value_set>(value_set_idx), // Param pack expansion kernel here.
2038 // Initial (baseline) parse: no need to apply baseline state.
2039 static_cast<const S_d_value_set*>(0), // (And here.)
2040 cfg_path, all_opt_names,
2041 final_validator_func, // (And here.)
2042 &skip_parsing),
2043 ++value_set_idx,
2044 success // Stop apply_impl()ing on first failure.
2045 )
2046 );
2047 } // if (success) (but it may have become false inside)
2048
2049 if ((!commit) && success)
2050 {
2051 m_multi_src_update_in_progress = Update_type::S_STATIC_AND_DYNAMIC; // (Was either NONE or already this.)
2052 FLOW_LOG_INFO("Config_manager [" << *this << "]: Multi-source update has successfully begun or continued. "
2053 "Expecting more sources before we can canonicalize candidate(s).");
2054 return true;
2055 }
2056 // else if (commit || (!success))
2057
2058 /* !success => reject, regardless of commit (a failure aborts a multi-source update too).
2059 * commit => accept or reject depending on `success`.
2060 *
2061 * So next either canonicalize or reject each parse attempt depending on overall success (static *and* dynamic). */
2062 value_set_idx = 0;
2063 (
2064 ...,
2065 (
2066 /* Note: If parsing stopped before `value_set_idx` due to failure, rejecting Option_set value_set_idx is a
2067 * harmless no-op by Option_set docs. */
2068 option_set_canonicalize_or_reject
2069 (opt_set<S_d_value_set>(value_set_idx), // Param pack expansion kernel here.
2070 success, 0),
2071 ++value_set_idx
2072 )
2073 );
2074
2075 if (m_multi_src_update_in_progress != Update_type::S_NONE)
2076 {
2077 FLOW_LOG_INFO("Config_manager [" << *this << "]: Multi-source update is now finished.");
2078 m_multi_src_update_in_progress = Update_type::S_NONE;
2079 }
2080 // else { It was NONE and remains so, either due to failure or being a vanilla (single-source) update or both. }
2081
2082 if (!success)
2083 {
2084 return false;
2085 }
2086 // else if (success):
2087 assert(commit && "Maintenance bug? We should have returned above if `!commit` but success==true.");
2088
2089 FLOW_LOG_INFO("Config_manager [" << *this << "]: State that was just made canonical follows.");
2090 log_state();
2091
2092 /* For dynamic sets only -- we must memorize the current Value_set state into
2093 * m_d_baseline_value_sets[]. It shall be set as the baseline state before actually parsing dynamic config file
2094 * contents in apply_dynamic(), each time it is called subsequently. */
2095 bool dyn_else_st = false;
2096 value_set_idx = 0;
2097 size_t d_value_set_idx = 0;
2098 (
2099 ...,
2100 (
2101 /* As noted above, fill an m_d_baseline_value_sets[] but for dynamic sets only. Note that this will copy
2102 * a Value_set onto a Value_set (in its entirety). We could also replace the shared_ptr, but why bother?
2103 * The code would be longer, and it would be somewhat slower too (still a copy involved).
2104 *
2105 * Also skip any null() dynamic set. (This is `if (dyn_else_st) { ... }` in expression form.) */
2106 dyn_else_st
2107 // (This is `if (!(...).null()) { ... }` in expression form.)
2108 && (opt_set<S_d_value_set>(value_set_idx)->null() // Param pack expansion kernel here.
2109 || (*(d_baseline_value_set<S_d_value_set>(d_value_set_idx)) // (And here.)
2110 = opt_set<S_d_value_set>(value_set_idx)->values(), // (And here.)
2111 true),
2112 ++d_value_set_idx,
2113 true),
2114
2115 ++value_set_idx,
2116 dyn_else_st = !dyn_else_st
2117 )
2118 );
2119
2120 // Next: Eureka! Set the dynamic-values pointers for the first time.
2121 {
2122 Lock_guard<decltype(m_d_value_sets_mutex)> lock(m_d_value_sets_mutex);
2123 value_set_idx = 0;
2124 dyn_else_st = false;
2125 (
2126 ...,
2127 (
2128 // Just skip static ones. (This is `if (dyn_else_st) { ... }` in expression form.)
2129 dyn_else_st
2130 && (save_dynamic_value_set_locked<S_d_value_set> // Param pack expansion kernel here.
2131 (opt_set<S_d_value_set>(value_set_idx), value_set_idx / 2),
2132 true),
2133 ++value_set_idx,
2134 dyn_else_st = !dyn_else_st
2135 )
2136 );
2137 } // Lock_guard lock(m_d_value_sets_mutex);
2138
2139 // They shouldn't do apply_static_and_dynamic() except as the first apply*_dynamic() thing.
2140 assert((!m_dynamic_values_set)
2141 && "User apparently attempted apply_static_and_dynamic(), but dynamic config was already initialized OK.");
2142 m_dynamic_values_set = true;
2143
2144 // Finally invoke dynamic "change" listeners for the first time. (.null() => no listeners registered.)
2145 for (d_value_set_idx = 0; d_value_set_idx != S_N_D_VALUE_SETS; ++d_value_set_idx)
2146 {
2147 invoke_dynamic_change_listeners(d_value_set_idx, true); // true => initial.
2148 }
2149
2150 return true;
2151} // Config_manager::apply_static_and_dynamic_impl()
2152
2153template<typename... S_d_value_set>
2154template<typename... Value_set>
2156 (const fs::path& dynamic_cfg_path,
2157 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
2158 bool commit)
2159{
2160 return apply_dynamic_impl<Value_set...>(false, dynamic_cfg_path, final_validator_func..., commit);
2161}
2162
2163template<typename... S_d_value_set>
2164template<typename... Value_set>
2167 const fs::path& dynamic_cfg_path,
2168 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
2169 bool commit)
2170{
2171 return apply_dynamic_impl<Value_set...>(true, dynamic_cfg_path, final_validator_func..., commit);
2172}
2173
2174template<typename... S_d_value_set>
2175template<typename... Value_set>
2177 (bool allow_invalid_defaults,
2178 const fs::path& dynamic_cfg_path,
2179 const typename Final_validator_func<Value_set>::Type&... final_validator_func,
2180 bool commit)
2181{
2182 using util::Lock_guard;
2184 using boost::array;
2185 using std::flush;
2186
2187 if (m_dynamic_values_set)
2188 {
2189 FLOW_LOG_INFO("Config_manager [" << *this << "]: "
2190 "Request to apply dynamic config file [" << dynamic_cfg_path << "]. "
2191 "Initializing dynamic config? = [false]. "
2192 "This application (if successful) shall end with canonicalizing values? = [" << commit << "]; "
2193 "multi-source update already in progress "
2194 "(0=none) = [" << int(m_multi_src_update_in_progress) << "].");
2195 }
2196 else
2197 {
2198 FLOW_LOG_INFO("Config_manager [" << *this << "]: "
2199 "Request to apply dynamic config file [" << dynamic_cfg_path << "]. "
2200 "Initializing dynamic config? = [true]. "
2201 "Invalid defaults allowed if explicitly set in file? = [" << allow_invalid_defaults << "]. "
2202 "This application (if successful) shall end with canonicalizing values? = [" << commit << "]; "
2203 "multi-source update already in progress "
2204 "(0=none) = [" << int(m_multi_src_update_in_progress) << "].");
2205 }
2206
2207 reject_candidates_if_update_type_changed(Update_type::S_DYNAMIC);
2208
2209 /* (See class doc header Implementation section for explanation of fold-expression (..., ) trick.)
2210 *
2211 * Note, though, that (... && expr) (of, say, length 4) evaluates to (((expr1 && expr2) && expr3) && expr4);
2212 * which means it'll run through the `expr...i...`s until one evaluates to false; then it stops and evaluates
2213 * no more past that. */
2214
2215 size_t value_set_idx;
2216 bool success = true;
2217
2218 /* Optionally scan the defaults themselves for individual-option-validator validity if so requested and applicable.
2219 * However no need to repeat this, if a multi-source update was already in progress. */
2220 if ((!m_dynamic_values_set) && (!allow_invalid_defaults) && (m_multi_src_update_in_progress == Update_type::S_NONE))
2221 {
2222 value_set_idx = 1; // 1, 3, 5, ....
2223 [[maybe_unused]] const auto dummy = // Defeats false gcc warning due to trailing `success` when N_D_VALUE_SETS is 1.
2224 (
2225 ...
2226 &&
2227 (
2228 opt_set<Value_set>(value_set_idx) // Param pack expansion kernel here.
2229 ->validate_values(&success), // Set `success`...
2230 value_set_idx += 2, // (Skip static ones.)
2231 success // ...and if it's false, the subsequent option sets shall be skipped.
2232 )
2233 );
2234
2235 if (!success) // It would have logged the exact problem, but we still want to clearly say what happened, high-level:
2236 {
2237 FLOW_LOG_WARNING("Config_manager [" << *this << "]: "
2238 "Request to initially-apply dynamic config file [" << dynamic_cfg_path << "]: "
2239 "Will not parse file, because stringent check "
2240 "of defaults detected at least 1 individually-invalid default value. Either correct "
2241 "the default or use apply_dynamic(allow_invalid_defaults_tag_t) overload.");
2242 }
2243 } // if ((!m_dynamic_values_set) && (!allow_invalid_defaults) && (m_multi_src_update_in_progress == NONE))
2244 /* else if (m_multi_src_update_in_progress != NONE) { It has been checked already; don't repeat. }
2245 * else if ((!m_dynamic_values_set) && allow_invalid_defaults) { They don't want default check: don't check it. }
2246 * else if (m_dynamic_values_set) { As advertised do not check defaults except possibly before first load. } */
2247
2248 /* Do the actual parsing of each dynamic Option_set. Recall that due to forward/backward-compatibility concerns
2249 * we allow any unknown option names, hence the empty set for all_opt_names. */
2250 success = success
2251 && apply_static_or_dynamic_impl<Value_set...>(true,
2252 dynamic_cfg_path, {}, final_validator_func...);
2253
2254 if ((!commit) && success)
2255 {
2256 m_multi_src_update_in_progress = Update_type::S_DYNAMIC; // (Was either NONE or already this.)
2257 FLOW_LOG_INFO("Config_manager [" << *this << "]: Multi-source update has successfully begun or continued. "
2258 "Expecting more sources before we can canonicalize candidate(s).");
2259 return true;
2260 }
2261 // else if (commit || (!success))
2262
2263 /* !success => reject, regardless of commit (a failure aborts a multi-source update too).
2264 * commit => accept or reject depending on `success`.
2265 *
2266 * So next either canonicalize or reject each parse attempt depending on overall success (dynamic only). */
2267 size_t d_value_set_idx = 0;
2268
2269 /* Zero it (`{}`) to avoid warnings from undefined behavior sanitizer; it’s not a perf-critical path.
2270 * (In reality the zeroing is not necessary, but we've run into warnings by some tools; might as well avoid.) */
2271 array<bool, S_N_D_VALUE_SETS> changed{};
2272 bool some_changed = false;
2273 (
2274 ...,
2275 (
2276 value_set_idx = (d_value_set_idx * 2) + 1, // 1, 3, 5, ....
2277 /* Note: If parsing stopped before `value_set_idx` due to failure, rejecting Option_set value_set_idx is a
2278 * harmless no-op by Option_set docs. */
2279 option_set_canonicalize_or_reject(opt_set<Value_set>(value_set_idx), // Param pack expansion kernel here.
2280 success,
2281 // If initial dynamic parse, then `changed[] = true` by definition.
2282 m_dynamic_values_set ? &changed[d_value_set_idx] : 0),
2283 // If initial dynamic parse, then `changed[] = true` by definition.
2284 changed[d_value_set_idx] = (!m_dynamic_values_set) || changed[d_value_set_idx],
2285 // some_changed = true iff: changed[x] == true for at least one x.
2286 some_changed = some_changed || changed[d_value_set_idx],
2287 ++d_value_set_idx
2288 )
2289 );
2290
2291 if (m_multi_src_update_in_progress != Update_type::S_NONE)
2292 {
2293 FLOW_LOG_INFO("Config_manager [" << *this << "]: Multi-source update is now finished.");
2294 m_multi_src_update_in_progress = Update_type::S_NONE;
2295 }
2296 // else { It was NONE and remains so, either due to failure or being a vanilla (single-source) update or both. }
2297
2298 if (!success)
2299 {
2300 return false;
2301 }
2302 // else
2303
2304 if (!some_changed) // Note, again, that if initial dynamic parse then some_changed==true.
2305 {
2306 return true; // Meaning, no changes -- but success.
2307 }
2308 // else
2309
2310 // The following is almost exactly log_state() but for dynamic sets only.
2311 String_ostream os;
2312 os.os() << "Config_manager [" << *this << "]: (Dynamic) state that was just made canonical follows.\n";
2313 value_set_idx = 1;
2314 (
2315 ...,
2316 (
2317 // 1, 3, 5, ....
2318 state_to_ostream_impl<Value_set>(value_set_idx, os.os()), // Param pack expansion kernel here.
2319 value_set_idx += 2
2320 )
2321 );
2322 os.os() << flush;
2323 FLOW_LOG_INFO(os.str());
2324
2325 /* Next: Eureka! Set the dynamic-values pointer for each option set that did change.
2326 *
2327 * There are some funky-looking logic-op/comma-expr constructions in there. I (ygoldfel) feel they're understandable
2328 * enough, and the conciseness is worth it, but arguably it could instead be done with regular `if ()`s instead
2329 * in a helper function -- though less concise (possible @todo). BTW generic lambdas could help there, but it would
2330 * require C++20 (I haven't looked into it thoroughly though). */
2331 {
2332 Lock_guard<decltype(m_d_value_sets_mutex)> lock(m_d_value_sets_mutex);
2333 d_value_set_idx = 0;
2334 (
2335 ...,
2336 (
2337 value_set_idx = (d_value_set_idx * 2) + 1, // 1, 3, 5, ....
2338 // Just skip unchanged ones. (This is `if (x) { f(...); }` in expression form.)
2339 changed[d_value_set_idx]
2340 && (save_dynamic_value_set_locked<Value_set> // Param pack expansion kernel here.
2341 (opt_set<Value_set>(value_set_idx), // (And here.)
2342 d_value_set_idx),
2343 true),
2344 ++d_value_set_idx
2345 )
2346 );
2347 } // Lock_guard lock(m_d_value_sets_mutex);
2348
2349 const bool was_dynamic_values_set = m_dynamic_values_set;
2350 m_dynamic_values_set = true; // Might already be true (if not initial parse).
2351
2352 /* Finally invoke dynamic change (or "change," if initial parse) listeners (possibly for the first time).
2353 * (.null() => no listeners registered.) */
2354 for (d_value_set_idx = 0; d_value_set_idx != S_N_D_VALUE_SETS; ++d_value_set_idx)
2355 {
2356 if (changed[d_value_set_idx])
2357 {
2358 invoke_dynamic_change_listeners(d_value_set_idx, !was_dynamic_values_set);
2359 }
2360 }
2361
2362 return true;
2363} // Config_manager::apply_dynamic()
2364
2365template<typename... S_d_value_set>
2367{
2368 if (m_multi_src_update_in_progress == Update_type::S_NONE)
2369 {
2370 FLOW_LOG_INFO("Config_manager [" << *this << "]: Request to abandon multi-source update in-progress; but none "
2371 "is in progress. Doing nothing.");
2372 return; // Nothing to cancel.
2373 }
2374 // else
2375
2376 FLOW_LOG_INFO("Config_manager [" << *this << "]: Request to abandon multi-source update in-progress. Doing so.");
2377
2378 // (See class doc header Implementation section for explanation of fold-expression (..., ) trick.)
2379 size_t value_set_idx = 0;
2380 (
2381 ...,
2382 (
2383 /* This will be silent if it is a no-op for a given Value_set; namely if it's a Null_value_set, and/or
2384 * it pertains to a static Value_set after an apply_dynamic() or dynamic after an apply_static(). */
2385 option_set_canonicalize_or_reject
2386 (opt_set<S_d_value_set>(value_set_idx), // Param pack expansion kernel here.
2387 false, 0), // false <= reject.
2388 ++value_set_idx
2389 )
2390 );
2391
2392 m_multi_src_update_in_progress = Update_type::S_NONE;
2393} // Config_manager::reject_candidates()
2394
2395template<typename... S_d_value_set>
2397{
2398 assert(this_update_type != Update_type::S_NONE);
2399 if ((m_multi_src_update_in_progress == Update_type::S_NONE) // Nothing to cancel; all is cool.
2400 || (m_multi_src_update_in_progress == this_update_type)) // It's a multi-source update; all is cool.
2401 {
2402 return; // Nothing to cancel.
2403 }
2404 // else if (m_multi_src_update_in_progress is truthy but != this_update_type)
2405
2406 FLOW_LOG_INFO("Config_manager [" << *this << "]: An update of type [" << int(this_update_type) << "] "
2407 "is interrupting (canceling) an in-progress multi-source update of type "
2408 "[" << int(m_multi_src_update_in_progress) << "]. Reverting any uncommitted values "
2409 "from the latter. Suggest invoking reject_candidates() explicitly if intentional; this "
2410 "message will then go away, while the code will be more deliberate.");
2411
2412 reject_candidates();
2413 assert(m_multi_src_update_in_progress == Update_type::S_NONE);
2414} // Config_manager::reject_candidates_if_update_type_changed()
2415
2416template<typename... S_d_value_set>
2417template<typename Value_set>
2419 (Option_set<Value_set>* opt_set_ptr, size_t d_value_set_idx)
2420{
2421 assert(opt_set_ptr);
2422 auto& opt_set = *opt_set_ptr;
2423
2424 /* Make a copy of the canonical dynamic Value_set, wrapped in ptr-to-mutable; upgrade to ptr-to-immutable.
2425 * @todo It's not-great this is inside the locked section. There's no great alternative -- maybe make all these
2426 * copies before the locked section, which can be done; it's just rather hairy due to the param-pack expanding. */
2427 *(static_cast<typename Value_set::Const_ptr*>(m_d_value_sets[d_value_set_idx].get()))
2428 // Attn: --^ void* -> T* cast; no RTTI to check our type safety.
2429 = Value_set::const_ptr_cast(opt_set.mutable_values_copy());
2430}
2431
2432template<typename... S_d_value_set>
2434{
2435 FLOW_LOG_INFO("Config_manager [" << *this << "]: "
2436 "Dynamic update for dynamic option set [" << d_value_set_idx << "] (0-based) of "
2437 "[" << S_N_D_VALUE_SETS << "] (1-based); trigger was "
2438 "[" << (init ? "initial-update" : "change-detected") << "].");
2439
2440 const auto& on_dynamic_change_funcs = m_on_dynamic_change_funcs[d_value_set_idx];
2441
2442 size_t idx = 0;
2443 for (const auto& on_dynamic_change_func : on_dynamic_change_funcs)
2444 {
2445 FLOW_LOG_INFO("Invoking listener [" << idx << "] (0-based) of [" << on_dynamic_change_funcs.size() << "] "
2446 "(1-based).");
2447 on_dynamic_change_func();
2448 ++idx;
2449 }
2450 FLOW_LOG_INFO("Done invoking change listeners for dynamic option set.");
2451} // Config_manager::invoke_dynamic_change_listeners()
2452
2453template<typename... S_d_value_set>
2454template<typename... Value_set>
2455void Config_manager<S_d_value_set...>::all_static_values(const Value_set**... value_set_or_null) const
2456{
2457 static_assert(sizeof...(Value_set) == S_N_S_VALUE_SETS,
2458 "You must supply N/2 `value_set_or_null`s, where N is # of S_d_value_set params. "
2459 "Use nullptr for any Null_value_set and/or Value_set of no interest.");
2460
2461 size_t s_value_set_idx = 0;
2462 size_t value_set_idx;
2463 (
2464 ...,
2465 (
2466 value_set_idx = s_value_set_idx * 2,
2467 // I.e., if (x && y) { *x = ... }.
2468 (value_set_or_null && (!opt_set<Value_set> // Param pack expansion kernel here (x2).
2469 (value_set_idx)->null()))
2470 && (*value_set_or_null = &(opt_set<Value_set>(value_set_idx)->values()), // (And here, x2.)
2471 true),
2472 ++s_value_set_idx
2473 )
2474 );
2475} // Config_manager::all_static_values()
2476
2477template<typename... S_d_value_set>
2478template<typename... Value_set>
2479void Config_manager<S_d_value_set...>::all_static_values_candidates(const Value_set**... value_set_or_null) const
2480{
2481 static_assert(sizeof...(Value_set) == S_N_S_VALUE_SETS,
2482 "You must supply N/2 `value_set_or_null`s, where N is # of S_d_value_set params. "
2483 "Use nullptr for any Null_value_set and/or Value_set of no interest.");
2484
2485 size_t s_value_set_idx = 0;
2486 size_t value_set_idx;
2487 (
2488 ...,
2489 (
2490 value_set_idx = s_value_set_idx * 2,
2491 (value_set_or_null && (!opt_set<Value_set> // Param pack expansion kernel here (x2).
2492 (value_set_idx)->null()))
2493 /* Emitted pointer will be equivalent to what is provided by all_static_values() unless a
2494 * parsed-but-not-canonicalized value set is available (i.e. this is being called from a validator function
2495 * being executed during parsing). This is achieved here because Option_set<>::values_candidate() will return
2496 * the candidate when in PARSING state, but nullptr when in CANONICAL state (in which case the canonical values
2497 * provided by Option_set<>::values() is then used, here, instead). */
2498 && (*value_set_or_null = opt_set<Value_set>(value_set_idx)->values_candidate() // (And here x2.)
2499 // (?: with omitted middle operand is gcc extension).
2500 ?: &(opt_set<Value_set>(value_set_idx)->values()), // (And here.)
2501 true),
2502 ++s_value_set_idx
2503 )
2504 );
2505} // Config_manager::all_static_values_candidates()
2506
2507template<typename... S_d_value_set>
2508template<typename Value_set>
2509const Value_set& Config_manager<S_d_value_set...>::static_values(size_t s_value_set_idx) const
2510{
2511 assert((s_value_set_idx < S_N_S_VALUE_SETS) && "Invalid static option set index.");
2512
2513 return opt_set<Value_set>(s_value_set_idx * 2)->values();
2514} // Config_manager::static_values()
2515
2516template<typename... S_d_value_set>
2517template<typename Value_set>
2518const Value_set& Config_manager<S_d_value_set...>::static_values_candidate(size_t s_value_set_idx) const
2519{
2520 assert((s_value_set_idx < S_N_S_VALUE_SETS) && "Invalid static option set index.");
2521
2522 const auto opt = opt_set<Value_set>(s_value_set_idx * 2);
2523
2524 /* Return value will be equivalent to what is provided by static_values() unless a parsed-but-not-canonicalized value
2525 * set is available (i.e. this is being called from a validator function being executed during parsing). This is
2526 * achieved here because ->values_candidate() will return the candidate when in PARSING state, but nullptr when in
2527 * CANONICAL state (in which case the canonical values provided by ->values() is used, instead). */
2528 const Value_set* values = opt->values_candidate();
2529 if (!values)
2530 {
2531 values = &(opt->values());
2532 }
2533
2534 return *values;
2535} // Config_manager::static_values_candidate()
2536
2537template<typename... S_d_value_set>
2538template<typename... Value_set>
2540 (typename Value_set::Const_ptr*... value_set_or_null) const
2541{
2542 using util::Lock_guard;
2543
2544 static_assert(sizeof...(Value_set) == S_N_D_VALUE_SETS,
2545 "You must supply N/2 `value_set_or_null`s, where N is # of S_d_value_set params. "
2546 "Use nullptr for any Null_value_set and/or Value_set of no interest.");
2547
2548 // As promised just copy out the values atomically and return them. Next time it might return different ones.
2549 Lock_guard<decltype(m_d_value_sets_mutex)> lock(m_d_value_sets_mutex);
2550
2551 size_t d_value_set_idx = 0;
2552 size_t value_set_idx;
2553 (
2554 ...,
2555 (
2556 value_set_idx = (d_value_set_idx * 2) + 1,
2557 // I.e., if (x && y) { *x = ... }.
2558 (value_set_or_null && (!opt_set<Value_set> // Param pack expansion kernel here (x2).
2559 (value_set_idx)->null()))
2560 && (*value_set_or_null // (And here.)
2561 = *(static_cast<typename Value_set::Const_ptr*>(m_d_value_sets[d_value_set_idx].get())), // (And here.)
2562 // Attn: --^ void* -> T* cast; no RTTI to check our type safety.
2563 true),
2564 ++d_value_set_idx
2565 )
2566 );
2567} // Config_manager::all_dynamic_values()
2568
2569template<typename... S_d_value_set>
2570template<typename Value_set>
2571typename Value_set::Const_ptr
2573{
2574 using util::Lock_guard;
2575
2576 assert((d_value_set_idx < S_N_D_VALUE_SETS) && "Invalid dynamic option set index.");
2577
2578 // As promised just copy out the value atomically and return it. Next time it might return different pointer.
2579 Lock_guard<decltype(m_d_value_sets_mutex)> lock(m_d_value_sets_mutex);
2580 return *(static_cast<typename Value_set::Const_ptr*>(m_d_value_sets[d_value_set_idx].get()));
2581 // Attn: --^ void* -> T* cast; no RTTI to check our type safety.
2582}
2583
2584template<typename... S_d_value_set>
2586{
2587 os << "Config_manager [" << *this << "]: Current state + config documentation:\n";
2588
2589 size_t value_set_idx = 0;
2590 (
2591 ...,
2592 state_to_ostream_impl<S_d_value_set>(value_set_idx++, os) // Param pack expansion kernel here.
2593 );
2594}
2595
2596template<typename... S_d_value_set>
2597template<typename Value_set>
2598void Config_manager<S_d_value_set...>::state_to_ostream_impl(size_t value_set_idx, std::ostream& os) const
2599{
2600 const auto& opts = *(opt_set<Value_set>(value_set_idx));
2601 if (opts.null())
2602 {
2603 return; // Print nothing at all.
2604 }
2605 // else
2606
2607 const size_t s_d_value_set_idx = value_set_idx / 2;
2608 const bool dyn_else_st = (value_set_idx % 2) == 1;
2609
2610 os << "-- [" << (dyn_else_st ? "DYNAMIC" : "STATIC") << "] [" << s_d_value_set_idx << "] --\n";
2611
2612 /* Helper that prints any pending commit=false data that hasn't been upgraded to canonical via
2613 * apply_*(commit = true). */
2614 const auto output_uncanonicalized = [&]()
2615 {
2616 if (m_multi_src_update_in_progress != Update_type::S_NONE)
2617 {
2618 /* The apply_*() sequence in-progress may be apply_dynamic(), while we're static, or vice versa.
2619 * In that case `Option_set opts` is in PARSING, not CANONICAL state, so there is nothing to print. */
2620 const auto candidate_or_none = opts.values_candidate();
2621 if (candidate_or_none)
2622 {
2623 os << " <incomplete multi-source update type [" << int(m_multi_src_update_in_progress) << "] in-progress / "
2624 "current candidate values follow>\n";
2625 opts.values_to_ostream(os, candidate_or_none);
2626 }
2627 }
2628 }; // output_uncanonicalized =
2629
2630 if (!dyn_else_st)
2631 {
2632 opts.values_to_ostream(os);
2633 output_uncanonicalized();
2634 return;
2635 }
2636 // else if (dyn_else_st)
2637
2638 if (m_dynamic_values_set)
2639 {
2640 opts.values_to_ostream(os);
2641 output_uncanonicalized();
2642 return;
2643 }
2644 // else if (no dynamic values yet loaded)
2645
2646 /* Print just help (no current values: we have none -- only defaults).
2647 * Granted the same could be said of static sets too, before apply_static*(), but keeping track of
2648 * a hypothetical m_static_values_set just to be able to do this same thing here for static options, for
2649 * something with such a simple usage lifecycle (apply_static*() is done exactly once, probably near the top
2650 * of the module/application), seems like overkill. */
2651 os << "<no dynamic config init payload / option docs follow instead>\n";
2652 opts.help_to_ostream(os);
2653
2654 output_uncanonicalized();
2655} // Config_manager::state_to_ostream_impl()
2656
2657template<typename... S_d_value_set>
2659{
2661 using std::flush;
2662
2663 String_ostream os;
2664 state_to_ostream(os.os());
2665 os.os() << flush;
2666 FLOW_LOG_WITH_CHECKING(sev, os.str());
2667} // Config_manager::log_state()
2668
2669template<typename... S_d_value_set>
2671{
2672 os << "Config_manager [" << *this << "]: Config help:\n";
2673
2674 size_t value_set_idx = 0;
2675 (
2676 ...,
2677 help_to_ostream_impl<S_d_value_set>(value_set_idx++, os) // Param pack expansion kernel here.
2678 );
2679}
2680
2681template<typename... S_d_value_set>
2682template<typename Value_set>
2683void Config_manager<S_d_value_set...>::help_to_ostream_impl(size_t value_set_idx, std::ostream& os) const
2684{
2685 const auto opts = opt_set<Value_set>(value_set_idx);
2686 if (opts->null())
2687 {
2688 return; // Print nothing at all.
2689 }
2690 // else
2691
2692 const size_t s_d_value_set_idx = value_set_idx / 2;
2693 const bool dyn_else_st = (value_set_idx % 2) == 1;
2694
2695 os << "-- [" << (dyn_else_st ? "DYNAMIC" : "STATIC") << "] [" << s_d_value_set_idx << "] --\n";
2696 opts->help_to_ostream(os);
2697} // Config_manager::help_to_ostream_impl()
2698
2699template<typename... S_d_value_set>
2701{
2703 using std::flush;
2704
2705 String_ostream os;
2706 help_to_ostream(os.os());
2707 os.os() << flush;
2708 FLOW_LOG_WITH_CHECKING(sev, os.str());
2709} // Config_manager::log_help()
2710
2711template<typename Value_set>
2713{
2714 return [](const Value_set&) -> Final_validator_outcome { return Final_validator_outcome::S_ACCEPT; };
2715}
2716
2717template<typename... S_d_value_set>
2718std::ostream& operator<<(std::ostream& os, const Config_manager<S_d_value_set...>& val)
2719{
2720 return os << '[' << val.m_nickname << "]@" << &val;
2721}
2722
2723} // namespace flow::cfg
Manages a config setup, intended for a single daemon process, by maintaining 1 or more set(s) of stat...
void log_help(log::Sev sev=log::Sev::S_INFO) const
Logs what help_to_ostream() would print.
boost::array< std::list< On_dynamic_change_func >, S_N_D_VALUE_SETS > m_on_dynamic_change_funcs
List of callbacks to execute after m_d_value_sets members (the pointers) are next assigned: one per e...
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 ...
void log_state(log::Sev sev=log::Sev::S_INFO) const
Logs what state_to_ostream() would print.
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 includi...
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 – applica...
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...
void option_set_canonicalize_or_reject(Option_set< Value_set > *opt_set, bool canonicalize_else_reject, bool *changed_or_null)
Helper that executes opt_set->canonicalize_candidate() or opt_set->reject_candidate().
Option_set< Value_set > * opt_set(size_t value_set_idx)
Helper that obtains the Option_set in the slot m_s_d_opt_sets[value_set_idx], which stores an Option_...
boost::array< Void_ptr, S_N_D_VALUE_SETS > m_d_value_sets
The dynamic config ref-counted handles returned by all_dynamic_values().
boost::array< Void_ptr, S_N_D_VALUE_SETS > m_d_baseline_value_sets
The baseline dynamic value sets, in the same order as S_N_D_VALUE_SETS dynamic S_d_value_set template...
void unregister_dynamic_change_listener(const On_dynamic_change_func_handle &handle)
Remove a previously registered dynamic change callback.
boost::array< Void_ptr, S_N_VALUE_SETS > m_s_d_opt_sets
The static and dynamic value sets, in the same order as the S_N_VALUE_SETS S_d_value_set template arg...
void invoke_dynamic_change_listeners(size_t d_value_set_idx, bool init) const
Invokes the registered listeners for the given dynamic config slot (synchronously).
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 ...
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...
bool apply_static_and_dynamic_impl(bool allow_invalid_defaults, const fs::path &cfg_path, const typename Final_validator_func< S_d_value_set >::Type &... final_validator_func, bool commit)
Implements all apply_static_and_dynamic() overloads.
allow_invalid_defaults_tag_t
Tag type: indicates an apply_*() method must allow invalid defaults and only complain if the config s...
@ S_ALLOW_INVALID_DEFAULTS
Sole value for tag type allow_invalid_defaults_tag_t.
const std::string m_nickname
See nickname ctor arg.
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...
boost::shared_ptr< void > Void_ptr
Short-hand for shared_ptr-to-void type used to store variable-type values in internal containers.
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 through...
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...
void save_dynamic_value_set_locked(Option_set< Value_set > *opt_set, size_t d_value_set_idx)
Little helper that, having assumed m_d_value_sets_mutex is locked, makes a newly allocated copy of th...
bool apply_dynamic_impl(bool allow_invalid_defaults, const fs::path &dynamic_cfg_path, const typename Final_validator_func< Value_set >::Type &... final_validator_func, bool commit)
Implements all apply_dynamic() overloads.
void help_to_ostream_impl(size_t value_set_idx, std::ostream &os) const
Helper of help_to_ostream() for m_s_d_opt_sets slot value_set_idx.
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 =...
static constexpr size_t S_N_S_VALUE_SETS
The number of static value sets (including any Null_value_sets).
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 s...
Update_type
Useful at least in the context of multi-source (commit == false) apply_*() methods,...
bool m_dynamic_values_set
Starts false; set to true permanently on successful apply_static_and_dynamic() or apply_dynamic().
bool apply_static_or_dynamic_impl(bool dyn_else_st, const fs::path &cfg_path, const boost::unordered_set< std::string > &all_opt_names_or_empty, const typename Final_validator_func< Value_set >::Type &... final_validator_func)
Work-horse helper that parses either all static value sets or all dynamic value sets from the specifi...
util::Mutex_non_recursive m_d_value_sets_mutex
Mutex protecting m_d_value_sets.
Value_set * d_baseline_value_set(size_t d_value_set_idx)
Helper that obtains the baseline dynamic Value_set in the slot m_d_baseline_value_sets[value_set_idx]...
void reject_candidates_if_update_type_changed(Update_type this_update_type)
Helper for the top of apply_*() that guards against a call to apply_Y() following apply_X(commit == f...
void reject_candidates()
Cancel a not-yet-canonicalized (incomplete) multi-source update, if one is in progress.
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 al...
static constexpr size_t S_N_D_VALUE_SETS
The number of dynamic value sets (including any Null_value_sets).
bool apply_impl(Option_set< Value_set > *opt_set, const Value_set *baseline_value_set_or_null, const fs::path &cfg_path, const boost::unordered_set< std::string > &all_opt_names_or_empty, const typename Final_validator_func< Value_set >::Type &final_validator_func, bool *skip_parsing)
Work-horse helper that parses into one given Option_set in m_s_d_opt_sets (from opt_set()) from the s...
Update_type m_multi_src_update_in_progress
In short, truthy if and only if a commit == false update is currently in progress,...
bool apply_static_impl(bool allow_invalid_defaults, const fs::path &static_cfg_path, const typename Final_validator_func< Value_set >::Type &... final_validator_func, bool commit)
Implements all apply_static() overloads.
static constexpr size_t S_N_VALUE_SETS
The number of template params in this Config_manager instantiation. It must be even and positive.
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 f...
void help_to_ostream(std::ostream &os) const
Prints a human-targeted long-form usage message that includes all options with their descriptions and...
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() o...
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 stringen...
const Option_set< Value_set > * opt_set(size_t value_set_idx) const
const overload of the other opt_set() helper.
void state_to_ostream_impl(size_t value_set_idx, std::ostream &os) const
Helper of state_to_ostream() for m_s_d_opt_sets slot value_set_idx.
The core config-parsing facility, which builds parsing/comparison/output capabilities on top of a giv...
Definition: option_set.hpp:422
const Values & values() const
Externally immutable internally stored canonical (current) config values as last constructed or parse...
void canonicalize_candidate(bool *change_detected=0)
In PARSING state enters CANONICAL state, finalizing values() from values_candidate().
void reject_candidate()
In PARSING state, returns to CANONICAL state, as if no parse attempts have occurred.
bool null() const
Return true if and only if the option-declaring function passed to the constructor declared no option...
const Values * values_candidate() const
Returns null in CANONICAL state; or in PARSING state a pointer to the not-yet-canonical values after ...
void parse_direct_values(const Values &src_values)
Enters into (from CANONICAL state) or continues in PARSING state by simply setting *values_candidate(...
Convenience class that simply stores a Logger and/or Component passed into a constructor; and returns...
Definition: log.hpp:1619
Logger * get_logger() const
Returns the stored Logger pointer, particularly as many FLOW_LOG_*() macros expect.
Definition: log.cpp:224
Interface that the user should implement, passing the implementing Logger into logging classes (Flow'...
Definition: log.hpp:1291
Convenience class template that endows the given subclass T with nested aliases Ptr and Const_ptr ali...
Similar to ostringstream but allows fast read-only access directly into the std::string being written...
#define FLOW_LOG_INFO(ARG_stream_fragment)
Logs an INFO message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:197
#define FLOW_LOG_WARNING(ARG_stream_fragment)
Logs a WARNING message into flow::log::Logger *get_logger() with flow::log::Component get_log_compone...
Definition: log.hpp:152
#define FLOW_LOG_WITH_CHECKING(ARG_sev, ARG_stream_fragment)
Logs a message of the specified severity into flow::log::Logger *get_logger() with flow::log::Compone...
Definition: log.hpp:489
#define FLOW_LOG_TRACE(ARG_stream_fragment)
Logs a TRACE message into flow::log::Logger *get_logger() with flow::log::Component get_log_component...
Definition: log.hpp:227
Flow module that facilitates configuring modules, such as applications and APIs, via statically and/o...
Definition: cfg_fwd.hpp:112
Final_validator_outcome
Result enumeration for a Final_validator_func::Type function which is used by a Config_manager user w...
@ S_ACCEPT
The holistically-checked cumulative Value_set has no problems and shall be accepted into the candidat...
@ S_SKIP
The holistically-checked cumulative Value_set has contents such that the validator function decided t...
@ S_FAIL
The holistically-checked cumulative Value_set has invalid contents; the candidate shall be rejected,...
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
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
std::ostream & operator<<(std::ostream &os, const Option_set< Value_set > &val)
Serializes (briefly) an Option_set to a standard output stream.
Sev
Enumeration containing one of several message severity levels, ordered from highest to lowest.
Definition: log_fwd.hpp:224
@ S_INFO
Message indicates a not-"bad" condition that is not frequent enough to be of severity Sev::S_TRACE.
boost::unique_lock< Mutex > Lock_guard
Short-hand for advanced-capability RAII lock guard for any mutex, ensuring exclusive ownership of tha...
Definition: util_fwd.hpp:265
std::string ostream_op_string(T const &... ostream_args)
Equivalent to ostream_op_to_string() but returns a new string by value instead of writing to the call...
Definition: util.hpp:356
boost::mutex Mutex_non_recursive
Short-hand for non-reentrant, exclusive mutex. ("Reentrant" = one can lock an already-locked-in-that-...
Definition: util_fwd.hpp:215
Basic_string_view< char > String_view
Commonly used char-based Basic_string_view. See its doc header.
Flow_log_component
The flow::log::Component payload enumeration comprising various log components used by Flow's own int...
Definition: common.hpp:632
Opaque handle for managing a dynamic config change callback.
std::list< On_dynamic_change_func >::const_iterator m_pos
Opaque handle detail.
Utility/traits type to concisely work with final-validation functions when calling methods like Confi...
Definition: cfg_manager.hpp:41
static Type null()
Returns a final-validator function that simply always returns Final_validator_outcome::S_ACCEPT.
Empty struct suitable as a *_value_set template arg for Config_manager, when a slot requires a Value_...
Definition: cfg_manager.hpp:73