userver: userver/utils/resource_scopes.hpp Source File
Loading...
Searching...
No Matches
resource_scopes.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/resource_scopes.hpp
4/// @brief @copybrief utils::ResourceScopeStorage
5
6#include <concepts>
7#include <cstdint>
8#include <memory>
9#include <optional>
10#include <vector>
11
12#include <userver/compiler/impl/lifetime.hpp>
13#include <userver/components/component_fwd.hpp>
14#include <userver/utils/assert.hpp>
15#include <userver/utils/impl/internal_tag.hpp>
16#include <userver/utils/move_only_function.hpp>
17
18USERVER_NAMESPACE_BEGIN
19
20namespace utils {
21
22namespace impl {
23class ScopeBase {
24public:
25 virtual ~ScopeBase() = default;
26
27 virtual void AfterConstruction() = 0;
28};
29
30template <typename Handle>
31class Scope final : public ScopeBase {
32public:
33 using AfterConstructionCallback = utils::move_only_function<Handle()>;
34
35 explicit Scope(AfterConstructionCallback after_construction)
36 : after_construction_(std::move(after_construction))
37 {}
38
39 void AfterConstruction() override { before_destruction_.emplace(after_construction_()); }
40
41private:
42 AfterConstructionCallback after_construction_;
43 std::optional<Handle> before_destruction_;
44};
45
46template <>
47class Scope<void> final : public ScopeBase {
48public:
49 using AfterConstructionCallback = utils::move_only_function<void()>;
50
51 explicit Scope(AfterConstructionCallback after_construction)
52 : after_construction_(std::move(after_construction))
53 {}
54
55 void AfterConstruction() override { after_construction_(); }
56
57private:
58 AfterConstructionCallback after_construction_;
59};
60
61/// @brief An object of ScopePtr defines actions to do after
62/// a component is constructed and just before it is destroyed.
63///
64/// @see @ref components::ComponentContext::Scopes
65using ScopePtr = std::unique_ptr<impl::ScopeBase>;
66
67} // namespace impl
68
69/// @brief Defers subscription and callback registration until the object is fully constructed.
70///
71/// Components often register external subscriptions (statistics writers, config listeners,
72/// and similar) that capture `this` and run later on another thread. Registering them
73/// directly in the constructor is unsafe: the callback may fire before the constructor
74/// finishes and observe partially initialized fields. Unregistering in the destructor
75/// is equally unsafe if the callback can still run while members are already being
76/// destroyed.
77///
78/// During construction, call @ref Register to queue a functor that performs the actual
79/// registration. The component system calls @ref AfterConstruction when the constructor
80/// (including derived classes) has completed, and @ref BeforeDestruction before the
81/// destructor body runs. That way registration callbacks see a fully built object, and
82/// unregistration runs before members used by the callback are torn down.
83///
84/// The same storage is available from @ref components::ComponentContext::Scopes in
85/// components, or as a standalone helper in unit tests and @ref WithResourceScopes.
86///
87/// @warning Do not store @ref ResourceScopeStorage as a field of the object that
88/// registers subscriptions on it. The storage would share that object's constructor
89/// and destructor, so @ref AfterConstruction cannot run after the object is complete
90/// and @ref BeforeDestruction cannot run before its members are destroyed. Wrap the
91/// object in @ref WithResourceScopes instead.
92///
93/// @snippet core/src/components/resource_scopes_test.cpp ResourceScopeStorage - HappyPathOrder
94class ResourceScopeStorage final {
95public:
96 ResourceScopeStorage() = default;
97
98 ResourceScopeStorage(ResourceScopeStorage&& other) noexcept = default;
99 ResourceScopeStorage& operator=(ResourceScopeStorage&& other) noexcept = default;
100
101 /// @brief Registers a functor to register some resource that will be
102 /// called after the component is successfully created (including all
103 /// class descendants) or after the component creation is emulated in
104 /// unit tests. The functor must return a RAII-style handle object
105 /// that unregisters the previously registered resource. The returned handle's
106 /// destructor is called just before the component destructor is called.
107 ///
108 /// @note callback is not called if the component is not created OR
109 /// any previously registered callback throws an exception.
110 /// @note if you don't have an existing RAII-ish class, but still want
111 /// to do a cleanup, you might want to use @ref utils::FastScopeGuard
112 /// to wrap the cleanup function.
113 template <std::invocable<> AfterConstructionCallback>
114 void Register(AfterConstructionCallback after_construction)
115 {
116 Register(utils::impl::InternalTag{}, Priority{0}, std::move(after_construction));
117 }
118
119 /// @cond
120 // For internal use only.
121 // Lower values run earlier in AfterConstruction and later in BeforeDestruction.
122 using Priority = std::int32_t;
123
124 template <std::invocable<> AfterConstructionCallback>
125 void Register(utils::impl::InternalTag, Priority priority, AfterConstructionCallback after_construction)
126 {
127 using Handle = std::invoke_result_t<AfterConstructionCallback>;
128 auto scope = std::make_unique<impl::Scope<Handle>>(std::move(after_construction));
129 DoRegister(std::move(scope), priority);
130 }
131 /// @endcond
132
133 /// @brief Call all registered functors.
134 ///
135 /// If a functor throws, already constructed resources are unregistered via
136 /// @ref BeforeDestruction and the exception is rethrown.
138
139 /// @brief Unregister all previously registered resources.
140 ///
141 /// Also drops factories that have not run @ref AfterConstruction yet,
142 /// so captured RAII handles unregister immediately.
143 void BeforeDestruction() noexcept;
144
145private:
146 struct ScopeWithPriority {
147 Priority priority{0};
148 impl::ScopePtr scope;
149 };
150
151 void DoRegister(impl::ScopePtr resource_scope, Priority priority);
152 static void SortByPriority(std::vector<ScopeWithPriority>& scopes) noexcept;
153
154 std::vector<ScopeWithPriority> registered_scopes_;
155 std::vector<impl::ScopePtr> initialized_scopes_;
156 bool scope_registration_finished_{false};
157};
158
159/// @brief A wrapper that provides @ref utils::ResourceScopeStorage for the wrapped object.
160///
161/// The wrapped object is passed `utils::ResourceScopeStorage&` as the first argument to the constructor.
162/// Prefer this over storing @ref ResourceScopeStorage as a field of the wrapped object itself.
163template <typename Wrapped>
164class WithResourceScopes final {
165public:
166 /// @brief Constructs the wrapped object and passes the embedded @ref utils::ResourceScopeStorage to it
167 /// as the first argument.
168 template <typename... Args>
169 explicit WithResourceScopes(std::in_place_t, Args&&... args)
170 : wrapped_(resource_scope_storage_, std::forward<Args>(args)...)
171 {
172 resource_scope_storage_.AfterConstruction();
173 }
174
175 WithResourceScopes(WithResourceScopes&& other) noexcept = default;
176 WithResourceScopes& operator=(WithResourceScopes&& other) noexcept = default;
177
178 ~WithResourceScopes() { resource_scope_storage_.BeforeDestruction(); }
179
180 /// @brief Returns the wrapped object.
181 Wrapped& operator*() & noexcept USERVER_IMPL_LIFETIME_BOUND { return wrapped_; }
182 /// @overload
183 const Wrapped& operator*() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return wrapped_; }
184
185 /// @brief Returns the wrapped object.
186 Wrapped* operator->() noexcept USERVER_IMPL_LIFETIME_BOUND { return &wrapped_; }
187 /// @overload
188 const Wrapped* operator->() const noexcept USERVER_IMPL_LIFETIME_BOUND { return &wrapped_; }
189
190private:
191 ResourceScopeStorage resource_scope_storage_;
192 Wrapped wrapped_;
193};
194
195ResourceScopeStorage&
196LocateDependency(components::WithType<ResourceScopeStorage>, const components::ComponentConfig& config, const components::ComponentContext&);
197
198} // namespace utils
199
200USERVER_NAMESPACE_END