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