Flow 2.0.0
Flow project: Full implementation reference.
action_registry.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
22#include "flow/common.hpp"
23#include <vector>
24
25namespace flow::util
26{
27
28/**
29 * A registry of callbacks (actions) sharing a common signature, intended for fan-out invocation
30 * across statically-constructed singletons. Each singleton self-registers a callback during its
31 * static construction; the end user then invokes execute() once to fan out to all registered callbacks.
32 *
33 * It is possible, and fine, to register actions from places other than singleton initializers, and the
34 * action itself is in no way required to do something singleton- or even global-related. The original
35 * motivation was for singleton self-registration, but conceivably Action_registry can be useful elsewhere.
36 *
37 * ### Motivation ###
38 * Consider a framework with multiple singleton subsystems, each needing a `set_logger()` call
39 * at startup and teardown. Without Action_registry the user must know about and call each one;
40 * and a new subsystem (possibly from a downstream project) cannot participate without modifying
41 * the framework's central code. With Action_registry each singleton registers itself, and
42 * the user's single execute() call reaches all of them -- including ones the framework author
43 * never anticipated.
44 *
45 * ### Usage ###
46 * Define a tag type to differentiate your registry from others:
47 *
48 * ~~~
49 * struct My_set_logger_tag {};
50 * using My_set_logger_registry = flow::util::Action_registry<My_set_logger_tag, flow::log::Logger*>;
51 * ~~~
52 *
53 * Each singleton registers during construction:
54 *
55 * ~~~
56 * // In some singleton's static-init context (ctor, Static_state ctor, etc.):
57 * My_set_logger_registry::register_action([](flow::log::Logger* logger_ptr)
58 * {
59 * Foo::static_set_logger(logger_ptr);
60 * });
61 * ~~~
62 *
63 * The end user simply does:
64 *
65 * ~~~
66 * My_set_logger_registry::execute(&my_logger); // startup
67 * // Forwarded `&my_logger` to all registered actions: Executed them in order of registration.
68 *
69 * // ... application runs ...
70 *
71 * My_set_logger_registry::execute(nullptr); // teardown
72 * // Forwarded `nullptr` to all registered actions: Executed them in order of registration.
73 * ~~~
74 *
75 * ### Arg types ###
76 * Each type in `Args` shall be a plain non-reference, non-`const` type -- typically a pointer or scalar.
77 * (Pointer *to* `const` is perfectly fine: e.g., `const Logger*`.) execute() receives
78 * each arg by `const` reference and passes it, as an lvalue, to each registered
79 * callback; since the callback signature takes `Args` by value, a copy is made per callback invocation.
80 * For pointer/scalar types this cost is trivial. For heavier types, prefer passing a pointer to the
81 * heavy object rather than the object itself.
82 *
83 * ### Thread safety ###
84 * All methods are thread-safe with respect to each other. Internally a non-recursive mutex is held
85 * during both register_action() and execute(). Consequently a callback invoked by execute()
86 * must not call register_action() on the same Action_registry instantiation; doing so will deadlock
87 * (this is intentional: such a call pattern would indicate a design problem).
88 *
89 * @tparam Tag_t
90 * A tag type used solely to differentiate distinct Action_registry instantiations.
91 * Each unique #Tag yields a distinct singleton registry.
92 * @tparam Args
93 * The parameter types of the action. Each registered callback has signature `void (Args...)`.
94 * See "Arg types" above.
95 */
96template<typename Tag_t, typename... Args>
98{
99public:
100 // Types.
101
102 /// Alias for template parameter.
103 using Tag = Tag_t;
104
105 // Methods.
106
107 /**
108 * Registers a callback to be invoked by future execute() calls. Intended to be called from
109 * singleton constructors (static-init time or lazy-init `get_instance()` patterns).
110 * Thread-safe.
111 *
112 * @tparam Action_func
113 * Functor type matching the signature `void (Args...)`.
114 * @param action
115 * The callback to register. Must not be empty.
116 */
117 template<typename Action_func>
118 static void register_action(Action_func&& action);
119
120 /**
121 * Invokes every registered callback, in registration order, passing the given arguments to each.
122 * Thread-safe.
123 *
124 * @param args
125 * Arguments forwarded to each registered callback.
126 */
127 static void execute(const Args&... args);
128
129private:
130 // Methods.
131
132 /**
133 * Returns the singleton instance. Uses a function-local `static`, so it is safe to call
134 * during static initialization from any translation unit.
135 *
136 * @return Reference to the sole instance.
137 */
139
140 // Data.
141
142 /// Protects #m_actions.
144
145 /**
146 * Registered callbacks, in registration order.
147 *
148 * If we wanted to add an `unregister_action()`, this might turn into a Linked_hash_set.
149 */
150 std::vector<Function<void (Args...)>> m_actions;
151}; // class Action_registry
152
153// Template implementations.
154
155template<typename Tag_t, typename... Args>
156template<typename Action_func>
157void Action_registry<Tag_t, Args...>::register_action(Action_func&& action) // Static.
158{
159 auto& self = get_instance();
160 Lock_guard<Mutex_non_recursive> lock{self.m_mutex};
161 self.m_actions.emplace_back(std::move(action));
162}
163
164template<typename Tag_t, typename... Args>
165void Action_registry<Tag_t, Args...>::execute(const Args&... args) // Static.
166{
167 const auto& self = get_instance();
168 Lock_guard<Mutex_non_recursive> lock{self.m_mutex};
169 for (const auto& action : self.m_actions)
170 {
171 action(args...);
172 }
173}
174
175template<typename Tag_t, typename... Args>
177{
178 static Action_registry s_inst; // Thread-safe at least in C++17 and better.
179 return s_inst;
180}
181
182} // namespace flow::util
A registry of callbacks (actions) sharing a common signature, intended for fan-out invocation across ...
static void register_action(Action_func &&action)
Registers a callback to be invoked by future execute() calls.
static void execute(const Args &... args)
Invokes every registered callback, in registration order, passing the given arguments to each.
std::vector< Function< void(Args...)> > m_actions
Registered callbacks, in registration order.
static Action_registry & get_instance()
Returns the singleton instance.
Mutex_non_recursive m_mutex
Protects m_actions.
Tag_t Tag
Alias for template parameter.
Flow module containing miscellaneous general-use facilities that don't fit into any other Flow module...
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
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