algorithms  2.2.0
Framework-independent algorithms for the EIC
Loading...
Searching...
No Matches
service.h
Go to the documentation of this file.
1// SPDX-License-Identifier: LGPL-3.0-or-later
2// Copyright (C) 2022 Wouter Deconinck, Sylvester Joosten
3//
4// Basic Service infrastructure, implemented as lazy-evaluated singletons (thread-safe).
5//
6// Provides the special ServiceSvc (that provides access to all instantiated services as
7// Configurable*), and the ServiceMixin (and related ALGORITHMS_DEFINE_SERVICE macro).
8//
9#pragma once
10
11#include <fmt/format.h>
12#include <fmt/ranges.h>
13#include <functional>
14#include <map>
15#include <mutex>
16#include <string>
17
18#include <algorithms/error.h>
19#include <algorithms/name.h>
20#include <algorithms/property.h>
21
22// Add boilerplate to service class definitions
23// - singleton --> no public constructor, no copy constructor, no assigmnent operator
24// - constructor should be protected so we can actually inherit from this class if needed
25// (mostly needed for the service base class)
26#define ALGORITHMS_DEFINE_SERVICE(className) \
27protected: \
28 className() : Service<className>(#className) {} \
29 \
30public: \
31 friend class Service<className>; \
32 className(const className&) = delete; \
33 void operator=(const className&) = delete; \
34 constexpr static const char* kName = #className;
35
36namespace algorithms {
37
38class ServiceError : public Error {
39public:
40 ServiceError(std::string_view msg) : Error{msg, "algorithms::ServiceError"} {}
41};
42
43class ServiceBase : public PropertyMixin {
44public:
45 // Is this service ready? (active and initialized)
46 bool ready() const { return m_ready; }
47 void ready(bool state) { m_ready = state; }
48
49private:
50 bool m_ready{false};
51};
52
53// Service service --> keeps track of all services :)
54// --> exposes the underlying ServiceBase* object of the service so
55// we can configure the services by name in the framework
56// boundary plugin
57// --> Special service (does not use the Service base class to avoid
58// circularity)
59class ServiceSvc : public NameMixin {
60public:
61 using ServiceMapType = std::map<std::string_view, ServiceBase*>;
62 static ServiceSvc& instance() {
63 // This is guaranteed to be thread-safe from C++11 onwards.
64 static ServiceSvc svc;
65 return svc;
66 }
67 // add a service to the ServiceSvc. Two options:
68 // - prior to init --> just add the service to the service stack and set the initializer
69 // - after init --> add service to call stack, set the initializer,
70 // manually call init for the service, revalidate
71 // The service must be fully constructed, as it is initialized right away after init.
72 template <class Svc>
73 void add(
74 ServiceBase* svc, const std::function<void(Svc&)>& init = [](Svc& s) { s.init(); }) {
77 }
78 // add a service to the service stack and set the initializer, without initializing it
79 // (used by the Service constructor, as the service is not yet fully constructed there)
80 template <class Svc>
82 ServiceBase* svc, const std::function<void(Svc&)>& init = [](Svc& s) { s.init(); }) {
83 std::lock_guard<std::recursive_mutex> lock{m_mutex};
84 m_keys.push_back(Svc::kName);
85 m_services[Svc::kName] = svc;
86 // only add initializer if not already present (e.g. if for some reason the framework did
87 // the registration early)
88 if (m_initializers.count(Svc::kName) == 0) {
90 }
91 }
92 // call init if we are already in an initialized state (this is a straggler)
93 template <class Svc> void initStraggler() {
94 std::lock_guard<std::recursive_mutex> lock{m_mutex};
95 if (m_init && !m_services.at(Svc::kName)->ready()) {
97 validate();
98 }
99 }
100
101 template <class Svc> bool has() const {
102 std::lock_guard<std::recursive_mutex> lock{m_mutex};
103 return m_services.count(Svc::kName);
104 }
105 // The initializer gets the registered service, so it does not need Svc::instance(), which
106 // may still be under construction
107 template <class Svc> void setInit(const std::function<void(Svc&)>& init) {
108 std::lock_guard<std::recursive_mutex> lock{m_mutex};
109 m_initializers[Svc::kName] = [init](ServiceBase* svc) { init(*static_cast<Svc*>(svc)); };
110 }
111
112 // Loop over all services in order and initialize one-by-one
113 // Finalize by validating that all is well
114 void init() {
115 std::lock_guard<std::recursive_mutex> lock{m_mutex};
116 // ensure we only call init once
117 if (m_init) {
118 throw ServiceError("Cannot initialize services twice");
119 }
120 // Call init for all the services and mark as ready
121 for (const auto& name : m_keys) {
122 auto svc = m_services.at(name);
123 try {
124 m_initializers.at(name)(svc);
125 } catch (const std::exception& e) {
126 // we encountered an issue, stop here so validation fails
127 break;
128 }
129 // Ensure our init made sense -- cannot have missing properties at this stage
130 if (svc->missingProperties().size() > 0) {
131 break;
132 }
133 svc->ready(true);
134 }
135
136 // Validate all services in case we encountered issues so we get useful error
137 // reporting
138 validate();
139
140 // Label initialization as complete
141 m_init = true;
142 }
143 // Single service init (meant to be called for stragglers after general init)
144 template <class Svc> void initSingle() {
145 std::lock_guard<std::recursive_mutex> lock{m_mutex};
146 std::string_view name = Svc::kName;
147 if (!m_init) {
148 throw ServiceError(
149 fmt::format("initSingle<{}>() should not be called before/instead of init()", name));
150 }
151 auto svc = m_services.at(name);
152 // ensure we only call init once
153 if (svc->ready()) {
154 throw ServiceError(fmt::format("Cannot initialize service {} - already initialized", name));
155 }
156 // call init
157 m_initializers.at(name)(svc);
158 // mark as ready
159 svc->ready(true);
160 }
161
162 template <class Svc = ServiceBase> Svc* service(std::string_view name) const {
163 std::lock_guard<std::recursive_mutex> lock{m_mutex};
164 return static_cast<Svc*>(m_services.at(name));
165 }
166 // Check if all service properties are set
167 // TODO FIXME move to implementation file
168 void validate() const {
169 std::lock_guard<std::recursive_mutex> lock{m_mutex};
170 std::map<std::string_view, std::vector<std::string_view>> missing_props;
171 std::vector<std::string_view> not_initialized;
172 for (std::string_view name : m_keys) {
173 const auto svc = m_services.at(name);
174 auto missing = svc->missingProperties();
175 if (!missing.empty()) {
176 missing_props[name] = missing;
177 }
178 if (!svc->ready()) {
179 not_initialized.push_back(name);
180 }
181 std::string err = "";
182 if (missing_props.size() > 0) {
183 err += fmt::format("Encountered missing service properties: {}\n", missing_props);
184 }
185 if (not_initialized.size() > 0) {
186 err += fmt::format("Encountered uninitialized services: {}\n", not_initialized);
187 }
188 if (err.size() > 0) {
189 throw ServiceError(fmt::format("Error initializing all services:\n{}", err));
190 }
191 }
192 }
193 // Not protected against concurrent registration of new services
194 const auto& services() const { return m_services; }
195 bool ready() const {
196 std::lock_guard<std::recursive_mutex> lock{m_mutex};
197 for (const auto& key : m_keys) {
198 if (!m_services.at(key)->ready()) {
199 return false;
200 }
201 }
202 return true;
203 }
204
205private:
206 ServiceSvc() : NameMixin{"ServiceSvc", "Special service that keeps track of all services"} {}
207
208public:
209 ServiceSvc(const ServiceSvc&) = delete;
210 void operator=(const ServiceSvc&) = delete;
211
212private:
213 std::vector<std::string_view> m_keys; // Ordered list of service keys
214 std::map<std::string_view, ServiceBase*> m_services; // Map of services for easier lookup
215 std::map<std::string_view, std::function<void(ServiceBase*)>> m_initializers; // Init calls
216 bool m_init = false; // did we initialize the services already?
217 // Services may be accessed for the first time from several threads concurrently. Recursive,
218 // as initializers can access (and thereby register) other services.
219 mutable std::recursive_mutex m_mutex;
220};
221
222// Thread-safe lazy-evaluated minimal service system
223// CRTP base class to add the instance method
224// This could have been part of DEFINE_SERVICE macro, but I think it is better
225// to keep the macro magic to a minimum to maximize transparency
226template <class SvcType> class Service : public ServiceBase, public NameMixin {
227public:
228 static SvcType& instance() {
229 // This is guaranteed to be thread-safe from C++11 onwards.
230 static SvcType& svc = []() -> SvcType& {
231 static SvcType s;
232 // services created after ServiceSvc::init() are initialized here, once they are fully
233 // constructed (initializing them from the constructor would be too early)
235 return s;
236 }();
237 return svc;
238 }
239 // constructor for the service base class registers the service, except
240 // for the ServiceSvc which is its own thing (avoid circularity)
241 // (services don't currently have an attached description)
242 Service(std::string_view name) : NameMixin{name, name} {
243 ServiceSvc::instance().registerService<SvcType>(this);
244 }
245};
246
247} // namespace algorithms
248
auto missingProperties() const
Definition property.h:56
Error(std::string_view msg, std::string_view type="algorithms::Error")
Definition error.h:15
NameMixin(std::string_view name, std::string_view description)
Definition name.h:15
std::string_view name() const
Definition name.h:17
bool ready() const
Definition service.h:46
void ready(bool state)
Definition service.h:47
ServiceError(std::string_view msg)
Definition service.h:40
static ServiceSvc & instance()
Definition service.h:62
ServiceSvc(const ServiceSvc &)=delete
bool has() const
Definition service.h:101
void operator=(const ServiceSvc &)=delete
std::map< std::string_view, ServiceBase * > ServiceMapType
Definition service.h:61
bool ready() const
Definition service.h:195
void add(ServiceBase *svc, const std::function< void(Svc &)> &init=[](Svc &s) { s.init();})
Definition service.h:73
void registerService(ServiceBase *svc, const std::function< void(Svc &)> &init=[](Svc &s) { s.init();})
Definition service.h:81
void setInit(const std::function< void(Svc &)> &init)
Definition service.h:107
Svc * service(std::string_view name) const
Definition service.h:162
void validate() const
Definition service.h:168
const auto & services() const
Definition service.h:194
Service(std::string_view name)
Definition service.h:242
static SvcType & instance()
Definition service.h:228
Configurable PropertyMixin
Definition property.h:206