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