Skip to content

Commit 4f37c91

Browse files
committed
feat components: implement ResourceScope
commit_hash:74196010ff67780c95ef08d1ba3eb81046385982
1 parent 9c711cd commit 4f37c91

File tree

9 files changed

+296
-4
lines changed

9 files changed

+296
-4
lines changed

.mapping.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,7 @@
871871
"core/include/userver/components/process_starter.hpp":"taxi/uservices/userver/core/include/userver/components/process_starter.hpp",
872872
"core/include/userver/components/raw_component_base.hpp":"taxi/uservices/userver/core/include/userver/components/raw_component_base.hpp",
873873
"core/include/userver/components/run.hpp":"taxi/uservices/userver/core/include/userver/components/run.hpp",
874+
"core/include/userver/components/scope.hpp":"taxi/uservices/userver/core/include/userver/components/scope.hpp",
874875
"core/include/userver/components/single_threaded_task_processors.hpp":"taxi/uservices/userver/core/include/userver/components/single_threaded_task_processors.hpp",
875876
"core/include/userver/components/state.hpp":"taxi/uservices/userver/core/include/userver/components/state.hpp",
876877
"core/include/userver/components/static_config_validator.hpp":"taxi/uservices/userver/core/include/userver/components/static_config_validator.hpp",
@@ -1323,6 +1324,7 @@
13231324
"core/src/components/minimal_server_component_list_test.cpp":"taxi/uservices/userver/core/src/components/minimal_server_component_list_test.cpp",
13241325
"core/src/components/process_starter.cpp":"taxi/uservices/userver/core/src/components/process_starter.cpp",
13251326
"core/src/components/process_starter.yaml":"taxi/uservices/userver/core/src/components/process_starter.yaml",
1327+
"core/src/components/resource_scope_test.cpp":"taxi/uservices/userver/core/src/components/resource_scope_test.cpp",
13261328
"core/src/components/run.cpp":"taxi/uservices/userver/core/src/components/run.cpp",
13271329
"core/src/components/single_threaded_task_processors.cpp":"taxi/uservices/userver/core/src/components/single_threaded_task_processors.cpp",
13281330
"core/src/components/single_threaded_task_processors.yaml":"taxi/uservices/userver/core/src/components/single_threaded_task_processors.yaml",

core/include/userver/components/component_context.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ class ComponentsLoadCancelledException : public std::runtime_error {
5959
explicit ComponentsLoadCancelledException(std::string_view message);
6060
};
6161

62+
namespace impl {
63+
class ScopeBase;
64+
}
65+
using ScopePtr = std::unique_ptr<impl::ScopeBase>;
66+
6267
/// @brief Class to retrieve other components.
6368
///
6469
/// Only the const member functions of this class are meant for usage in
@@ -152,6 +157,19 @@ class ComponentContext final {
152157
/// as an `std::string` if needed.
153158
std::string_view GetComponentName() const;
154159

160+
/// @brief Registers a functor to register some resource that will be
161+
/// called after the component is succesfully created (including all
162+
/// class descendants). The functor must return a RAII-style handle object
163+
/// that unregisters the previously registered resource. The returned handle's
164+
/// destructor is called just before the component destructor is called.
165+
///
166+
/// @note callback is not called if the component is not created OR
167+
/// any previously registered callback throws an exception.
168+
/// @note if you don't have an existing RAII-ish class, but still want
169+
/// to do a cleanup, you might want to use @ref utils::FastScopeGuard
170+
/// to wrap the cleanup function.
171+
void RegisterScope(ScopePtr) const;
172+
155173
/// @cond
156174
// For internal use only.
157175
ComponentContext(utils::impl::InternalTag, impl::ComponentContextImpl& impl, impl::ComponentInfo& component_info)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#pragma once
2+
3+
/// @file userver/components/scope.hpp
4+
/// @brief @copybrief components::MakeScope
5+
6+
#include <functional>
7+
#include <memory>
8+
#include <optional>
9+
10+
#include <userver/utils/assert.hpp>
11+
12+
USERVER_NAMESPACE_BEGIN
13+
14+
namespace components {
15+
16+
namespace impl {
17+
class ScopeBase {
18+
public:
19+
virtual ~ScopeBase() = default;
20+
21+
virtual void AfterConstruction() = 0;
22+
};
23+
24+
template <typename Handle>
25+
class Scope final : public ScopeBase {
26+
public:
27+
using AfterConstructionCallback = std::function<Handle()>;
28+
29+
explicit Scope(AfterConstructionCallback after_construction)
30+
: after_construction_(std::move(after_construction))
31+
{}
32+
33+
void AfterConstruction() override { before_destruction_.emplace(after_construction_()); }
34+
35+
private:
36+
AfterConstructionCallback after_construction_;
37+
std::optional<Handle> before_destruction_;
38+
};
39+
40+
template <>
41+
class Scope<void> final : public ScopeBase {
42+
public:
43+
using AfterConstructionCallback = std::function<void()>;
44+
45+
explicit Scope(AfterConstructionCallback after_construction)
46+
: after_construction_(std::move(after_construction))
47+
{}
48+
49+
void AfterConstruction() override { after_construction_(); }
50+
51+
private:
52+
AfterConstructionCallback after_construction_;
53+
};
54+
55+
} // namespace impl
56+
57+
/// @brief An object of ScopePtr defines actions to do after
58+
/// a component is constructed and just before it is destroyed.
59+
///
60+
/// @see @ref components::ComponentContext::RegisterScope
61+
using ScopePtr = std::unique_ptr<impl::ScopeBase>;
62+
63+
/// @brief Constructs an object of type @ref ScopePtr from
64+
/// a registration callback. The callback must return an object that undoes
65+
/// the registration in its destructor.
66+
///
67+
/// @see @ref components::ComponentContext::RegisterScope
68+
template <typename AfterConstructionCallback>
69+
ScopePtr MakeScope(AfterConstructionCallback after_construction)
70+
{
71+
using Handle = std::invoke_result_t<AfterConstructionCallback>;
72+
return std::make_unique<impl::Scope<Handle>>(std::move(after_construction));
73+
}
74+
75+
} // namespace components
76+
77+
USERVER_NAMESPACE_END

core/include/userver/components/tcp_acceptor_base.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ class TcpAcceptorBase : public ComponentBase {
6161

6262
void KeepAccepting(engine::io::Socket& listen_sock);
6363

64-
void OnAllComponentsLoaded() final;
65-
void OnAllComponentsAreStopping() final;
64+
void Start();
65+
void Stop() noexcept;
6666

6767
struct SocketData {
6868
engine::io::Socket listen_sock;

core/src/components/component_context.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <userver/components/component_context.hpp>
22

33
#include <components/component_context_impl.hpp>
4+
#include <userver/components/scope.hpp>
45

56
USERVER_NAMESPACE_BEGIN
67

@@ -25,6 +26,10 @@ engine::TaskProcessor& ComponentContext::GetTaskProcessor(std::string_view name)
2526

2627
std::string_view ComponentContext::GetComponentName() const { return component_info_.GetName(); }
2728

29+
void ComponentContext::RegisterScope(ScopePtr resource_scope) const {
30+
component_info_.RegisterScope(std::move(resource_scope));
31+
}
32+
2833
impl::ComponentContextImpl& ComponentContext::GetImpl(utils::impl::InternalTag) const { return impl_; }
2934

3035
const impl::Manager& ComponentContext::GetManager(utils::impl::InternalTag) const { return impl_.GetManager(); }

core/src/components/component_context_component_info.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <fmt/format.h>
44
#include <fmt/ranges.h>
5+
#include <boost/range/adaptor/reversed.hpp>
56
#include <boost/range/adaptor/transformed.hpp>
67

78
#include <userver/components/component_context.hpp>
@@ -46,12 +47,28 @@ void ComponentInfo::SetComponent(std::unique_ptr<RawComponentBase>&& component)
4647
call_on_loading_cancelled = true;
4748
}
4849
}
50+
51+
AfterConstruction();
52+
4953
if (call_on_loading_cancelled) {
5054
OnLoadingCancelled();
5155
}
5256
cv_.NotifyAll();
5357
}
5458

59+
void ComponentInfo::AfterConstruction()
60+
{
61+
// A tweak to be sure in case of parial initialization only
62+
// already initialized scopes' before_dtr() are called
63+
auto tmp_resource_scopes = std::move(resource_scopes_);
64+
resource_scopes_.clear(); // for clang-static-analyzer
65+
for (auto& resource_scope : tmp_resource_scopes) {
66+
resource_scope->AfterConstruction();
67+
68+
resource_scopes_.push_back(std::move(resource_scope));
69+
}
70+
}
71+
5572
void ComponentInfo::ClearComponent() {
5673
if (!HasComponent()) {
5774
return;
@@ -61,6 +78,13 @@ void ComponentInfo::ClearComponent() {
6178

6279
auto component = ExtractComponent();
6380
LOG_DEBUG() << "Stopping component";
81+
82+
// Call Scopes' pre-destruction callbacks in reverse order
83+
for (auto& scope : resource_scopes_ | boost::adaptors::reversed) {
84+
scope.reset();
85+
}
86+
resource_scopes_.clear();
87+
6488
component.reset();
6589
LOG_DEBUG() << "Stopped component";
6690
}
@@ -190,6 +214,11 @@ std::string ComponentInfo::GetDependencies() const {
190214
return fmt::format(R"("{}" -> "{}" )", name_, JoinNamesFromInfo(it_depends_on_, delimiter));
191215
}
192216

217+
void ComponentInfo::RegisterScope(ScopePtr resource_scope)
218+
{
219+
resource_scopes_.push_back(std::move(resource_scope));
220+
}
221+
193222
bool ComponentInfo::HasComponent() const {
194223
const std::lock_guard lock{mutex_};
195224
return !!component_;

core/src/components/component_context_component_info.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
#include <string>
88
#include <unordered_set>
99

10+
#include <userver/components/component_context.hpp>
1011
#include <userver/components/raw_component_base.hpp>
12+
#include <userver/components/scope.hpp>
1113
#include <userver/engine/condition_variable.hpp>
1214
#include <userver/engine/mutex.hpp>
1315
#include <userver/utils/not_null.hpp>
@@ -96,15 +98,19 @@ class ComponentInfo final {
9698

9799
std::string GetDependencies() const;
98100

101+
void RegisterScope(ScopePtr);
102+
99103
private:
100104
bool HasComponent() const;
101105
std::unique_ptr<RawComponentBase> ExtractComponent();
106+
void AfterConstruction();
102107

103108
const std::string name_;
104109

105110
mutable engine::Mutex mutex_;
106111
mutable engine::ConditionVariable cv_;
107112
std::unique_ptr<RawComponentBase> component_;
113+
std::vector<ScopePtr> resource_scopes_;
108114
std::set<ComponentInfoRef> it_depends_on_;
109115
std::set<ComponentInfoRef> depends_on_it_;
110116
ComponentLifetimeStage stage_ = ComponentLifetimeStage::kNull;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#include <userver/utest/utest.hpp>
2+
3+
#include <userver/components/component_base.hpp>
4+
#include <userver/components/component_context.hpp>
5+
#include <userver/components/minimal_component_list.hpp>
6+
#include <userver/components/run.hpp>
7+
#include <userver/components/scope.hpp>
8+
#include <userver/logging/log.hpp>
9+
#include <userver/utils/fast_scope_guard.hpp>
10+
11+
USERVER_NAMESPACE_BEGIN
12+
13+
namespace {
14+
15+
const std::string kConfig = R"(
16+
components_manager:
17+
coro_pool:
18+
initial_size: 50
19+
max_size: 500
20+
default_task_processor: main-task-processor
21+
fs_task_processor: main-task-processor
22+
event_thread_pool:
23+
threads: 1
24+
task_processors:
25+
main-task-processor:
26+
worker_threads: 1
27+
components:
28+
component: {}
29+
logging:
30+
loggers: {}
31+
)";
32+
33+
} // namespace
34+
35+
TEST(ComponentsScope, Smoke)
36+
{
37+
static bool init_called = false;
38+
static bool destroy_called = false;
39+
40+
class ComponentWithResource final : public components::ComponentBase {
41+
public:
42+
ComponentWithResource(const components::ComponentConfig& config, const components::ComponentContext& context)
43+
: components::ComponentBase(config, context)
44+
{
45+
context.RegisterScope(components::MakeScope([] {
46+
init_called = true;
47+
return utils::FastScopeGuard([]() noexcept { destroy_called = true; });
48+
}));
49+
}
50+
};
51+
52+
auto component_list = components::MinimalComponentList().Append<ComponentWithResource>("component");
53+
components::RunOnce(components::InMemoryConfig{kConfig}, component_list);
54+
55+
EXPECT_TRUE(init_called);
56+
EXPECT_TRUE(destroy_called);
57+
}
58+
59+
TEST(ComponentsScope, HappyPathOrder)
60+
{
61+
static std::vector<int> trace;
62+
63+
class ComponentWithResource final : public components::ComponentBase {
64+
public:
65+
ComponentWithResource(const components::ComponentConfig& config, const components::ComponentContext& context)
66+
: components::ComponentBase(config, context)
67+
{
68+
trace.push_back(0);
69+
70+
context.RegisterScope(components::MakeScope([] {
71+
trace.push_back(1);
72+
return utils::FastScopeGuard([]() noexcept { trace.push_back(2); });
73+
}));
74+
75+
context.RegisterScope(components::MakeScope([] {
76+
trace.push_back(3);
77+
return utils::FastScopeGuard([]() noexcept { trace.push_back(4); });
78+
}));
79+
}
80+
};
81+
82+
auto component_list = components::MinimalComponentList().Append<ComponentWithResource>("component");
83+
components::RunOnce(components::InMemoryConfig{kConfig}, component_list);
84+
85+
EXPECT_EQ(trace, (std::vector{0, 1, 3, 4, 2}));
86+
}
87+
88+
TEST(ComponentsScope, CtrThrow)
89+
{
90+
static std::vector<int> trace;
91+
92+
class ComponentWithResource final : public components::ComponentBase {
93+
public:
94+
ComponentWithResource(const components::ComponentConfig& config, const components::ComponentContext& context)
95+
: components::ComponentBase(config, context)
96+
{
97+
trace.push_back(0);
98+
99+
context.RegisterScope(components::MakeScope([] {
100+
trace.push_back(1);
101+
return utils::FastScopeGuard([]() noexcept { trace.push_back(2); });
102+
}));
103+
104+
context.RegisterScope(components::MakeScope([] {
105+
trace.push_back(3);
106+
return utils::FastScopeGuard([]() noexcept { trace.push_back(4); });
107+
}));
108+
109+
throw std::runtime_error("1");
110+
}
111+
};
112+
113+
auto component_list = components::MinimalComponentList().Append<ComponentWithResource>("component");
114+
EXPECT_THROW(components::RunOnce(components::InMemoryConfig{kConfig}, component_list), std::runtime_error);
115+
116+
EXPECT_EQ(trace, (std::vector{0}));
117+
}
118+
119+
TEST(ComponentsScope, CallbackThrow)
120+
{
121+
static std::vector<int> trace;
122+
123+
class ComponentWithResource final : public components::ComponentBase {
124+
public:
125+
ComponentWithResource(const components::ComponentConfig& config, const components::ComponentContext& context)
126+
: components::ComponentBase(config, context)
127+
{
128+
trace.push_back(0);
129+
130+
context.RegisterScope(components::MakeScope([] {
131+
trace.push_back(1);
132+
return utils::FastScopeGuard([]() noexcept { trace.push_back(2); });
133+
}));
134+
135+
context.RegisterScope(components::MakeScope([] {
136+
trace.push_back(3);
137+
throw std::runtime_error("1");
138+
return utils::FastScopeGuard([]() noexcept { trace.push_back(4); });
139+
}));
140+
}
141+
};
142+
143+
auto component_list = components::MinimalComponentList().Append<ComponentWithResource>("component");
144+
EXPECT_THROW(components::RunOnce(components::InMemoryConfig{kConfig}, component_list), std::runtime_error);
145+
146+
EXPECT_EQ(trace, (std::vector{0, 1, 3, 2}));
147+
}
148+
149+
USERVER_NAMESPACE_END

0 commit comments

Comments
 (0)