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 <memory>
8#include <optional>
9
10#include <userver/compiler/impl/lifetime.hpp>
11#include <userver/components/component_fwd.hpp>
12#include <userver/utils/assert.hpp>
13#include <userver/utils/move_only_function.hpp>
14
15USERVER_NAMESPACE_BEGIN
16
17namespace utils {
18
19namespace impl {
20class ScopeBase {
21public:
22 virtual ~ScopeBase() = default;
23
24 virtual void AfterConstruction() = 0;
25};
26
27template <typename Handle>
28class Scope final : public ScopeBase {
29public:
30 using AfterConstructionCallback = utils::move_only_function<Handle()>;
31
32 explicit Scope(AfterConstructionCallback after_construction)
33 : after_construction_(std::move(after_construction))
34 {}
35
36 void AfterConstruction() override { before_destruction_.emplace(after_construction_()); }
37
38private:
39 AfterConstructionCallback after_construction_;
40 std::optional<Handle> before_destruction_;
41};
42
43template <>
44class Scope<void> final : public ScopeBase {
45public:
46 using AfterConstructionCallback = utils::move_only_function<void()>;
47
48 explicit Scope(AfterConstructionCallback after_construction)
49 : after_construction_(std::move(after_construction))
50 {}
51
52 void AfterConstruction() override { after_construction_(); }
53
54private:
55 AfterConstructionCallback after_construction_;
56};
57
58/// @brief An object of ScopePtr defines actions to do after
59/// a component is constructed and just before it is destroyed.
60///
61/// @see @ref components::ComponentContext::Scopes
62using ScopePtr = std::unique_ptr<impl::ScopeBase>;
63
64} // namespace impl
65
66/// @brief Defers subscription and callback registration until the object is fully constructed.
67///
68/// Components often register external subscriptions (statistics writers, config listeners,
69/// and similar) that capture `this` and run later on another thread. Registering them
70/// directly in the constructor is unsafe: the callback may fire before the constructor
71/// finishes and observe partially initialized fields. Unregistering in the destructor
72/// is equally unsafe if the callback can still run while members are already being
73/// destroyed.
74///
75/// During construction, call @ref Register to queue a functor that performs the actual
76/// registration. The component system calls @ref AfterConstruction when the constructor
77/// (including derived classes) has completed, and @ref BeforeDestruction before the
78/// destructor body runs. That way registration callbacks see a fully built object, and
79/// unregistration runs before members used by the callback are torn down.
80///
81/// The same storage is available from @ref components::ComponentContext::Scopes in
82/// components, or as a standalone helper in unit tests and @ref WithResourceScopes.
83///
84/// @snippet core/src/components/resource_scopes_test.cpp ResourceScopeStorage - HappyPathOrder
85class ResourceScopeStorage final {
86public:
87 ResourceScopeStorage() = default;
88
89 ResourceScopeStorage(ResourceScopeStorage&& other) noexcept = default;
90 ResourceScopeStorage& operator=(ResourceScopeStorage&& other) noexcept = default;
91
92 /// @brief Registers a functor to register some resource that will be
93 /// called after the component is successfully created (including all
94 /// class descendants) or after the component creation is emulated in
95 /// unit tests. The functor must return a RAII-style handle object
96 /// that unregisters the previously registered resource. The returned handle's
97 /// destructor is called just before the component destructor is called.
98 ///
99 /// @note callback is not called if the component is not created OR
100 /// any previously registered callback throws an exception.
101 /// @note if you don't have an existing RAII-ish class, but still want
102 /// to do a cleanup, you might want to use @ref utils::FastScopeGuard
103 /// to wrap the cleanup function.
104 template <std::invocable<> AfterConstructionCallback>
105 void Register(AfterConstructionCallback after_construction)
106 {
107 using Handle = std::invoke_result_t<AfterConstructionCallback>;
108 auto scope = std::make_unique<impl::Scope<Handle>>(std::move(after_construction));
109 DoRegister(std::move(scope));
110 }
111
112 /// @brief Call all registered functors.
114
115 /// @brief Unregister all previously registered resources.
117
118private:
119 void DoRegister(impl::ScopePtr resource_scope);
120
121 std::vector<impl::ScopePtr> registered_scopes_;
122 std::vector<impl::ScopePtr> initialized_scopes_;
123 bool scope_registration_finished_{false};
124};
125
126/// @brief A wrapper that provides @ref utils::ResourceScopeStorage for the wrapped object.
127///
128/// The wrapped object is passed `utils::ResourceScopeStorage&` as the first argument to the constructor.
129template <typename Wrapped>
130class WithResourceScopes final {
131public:
132 /// @brief Constructs the wrapped object and passes the embedded @ref utils::ResourceScopeStorage to it
133 /// as the first argument.
134 template <typename... Args>
135 explicit WithResourceScopes(std::in_place_t, Args&&... args)
136 : wrapped_(resource_scope_storage_, std::forward<Args>(args)...)
137 {
138 resource_scope_storage_.AfterConstruction();
139 }
140
141 WithResourceScopes(WithResourceScopes&& other) noexcept = default;
142 WithResourceScopes& operator=(WithResourceScopes&& other) noexcept = default;
143
144 ~WithResourceScopes() { resource_scope_storage_.BeforeDestruction(); }
145
146 /// @brief Returns the wrapped object.
147 Wrapped& operator*() & noexcept USERVER_IMPL_LIFETIME_BOUND { return wrapped_; }
148 /// @overload
149 const Wrapped& operator*() const& noexcept USERVER_IMPL_LIFETIME_BOUND { return wrapped_; }
150
151 /// @brief Returns the wrapped object.
152 Wrapped* operator->() noexcept USERVER_IMPL_LIFETIME_BOUND { return &wrapped_; }
153 /// @overload
154 const Wrapped* operator->() const noexcept USERVER_IMPL_LIFETIME_BOUND { return &wrapped_; }
155
156private:
157 ResourceScopeStorage resource_scope_storage_;
158 Wrapped wrapped_;
159};
160
161ResourceScopeStorage&
162LocateDependency(components::WithType<ResourceScopeStorage>, const components::ComponentConfig& config, const components::ComponentContext&);
163
164} // namespace utils
165
166USERVER_NAMESPACE_END