Skip to content

Commit 3ccd299

Browse files
committed
feat core: catch signals before component system is up and running
commit_hash:41ef856353d591c431d57baaea86c4d0ac4a9378
1 parent f9be3c9 commit 3ccd299

File tree

10 files changed

+202
-45
lines changed

10 files changed

+202
-45
lines changed

.mapping.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,11 @@
751751
"core/functional_tests/metrics/tests/conftest.py":"taxi/uservices/userver/core/functional_tests/metrics/tests/conftest.py",
752752
"core/functional_tests/metrics/tests/static/metrics_values.txt":"taxi/uservices/userver/core/functional_tests/metrics/tests/static/metrics_values.txt",
753753
"core/functional_tests/metrics/tests/test_metrics.py":"taxi/uservices/userver/core/functional_tests/metrics/tests/test_metrics.py",
754+
"core/functional_tests/signal_during_boot/CMakeLists.txt":"taxi/uservices/userver/core/functional_tests/signal_during_boot/CMakeLists.txt",
755+
"core/functional_tests/signal_during_boot/service.cpp":"taxi/uservices/userver/core/functional_tests/signal_during_boot/service.cpp",
756+
"core/functional_tests/signal_during_boot/static_config.yaml":"taxi/uservices/userver/core/functional_tests/signal_during_boot/static_config.yaml",
757+
"core/functional_tests/signal_during_boot/tests/conftest.py":"taxi/uservices/userver/core/functional_tests/signal_during_boot/tests/conftest.py",
758+
"core/functional_tests/signal_during_boot/tests/test_rotate_logs.py":"taxi/uservices/userver/core/functional_tests/signal_during_boot/tests/test_rotate_logs.py",
754759
"core/functional_tests/slow_start/CMakeLists.txt":"taxi/uservices/userver/core/functional_tests/slow_start/CMakeLists.txt",
755760
"core/functional_tests/slow_start/config_vars.yaml":"taxi/uservices/userver/core/functional_tests/slow_start/config_vars.yaml",
756761
"core/functional_tests/slow_start/secure_data.json":"taxi/uservices/userver/core/functional_tests/slow_start/secure_data.json",

core/functional_tests/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-static-service)
3232
add_subdirectory(slow_start)
3333
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-slow-start)
3434

35+
add_subdirectory(signal_during_boot)
36+
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-signal-during-boot)
37+
3538
add_subdirectory(tracing)
3639
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}-tracing)
3740

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
project(userver-core-tests-signal-during-boot CXX)
2+
3+
add_executable(${PROJECT_NAME} service.cpp)
4+
target_link_libraries(${PROJECT_NAME} userver::core)
5+
6+
userver_chaos_testsuite_add()
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include <userver/utils/assert.hpp>
2+
3+
#include <userver/clients/dns/component.hpp>
4+
#include <userver/clients/http/component.hpp>
5+
#include <userver/components/component.hpp>
6+
#include <userver/components/minimal_server_component_list.hpp>
7+
#include <userver/engine/sleep.hpp>
8+
#include <userver/http/url.hpp>
9+
#include <userver/server/handlers/http_handler_base.hpp>
10+
#include <userver/server/handlers/tests_control.hpp>
11+
#include <userver/testsuite/testsuite_support.hpp>
12+
#include <userver/utils/daemon_run.hpp>
13+
#include <userver/yaml_config/merge_schemas.hpp>
14+
15+
#include <userver/utest/using_namespace_userver.hpp>
16+
17+
class SlowComponent final : public components::ComponentBase {
18+
public:
19+
static constexpr std::string_view kName = "slow-component";
20+
21+
SlowComponent(const components::ComponentConfig& config, const components::ComponentContext& context)
22+
: components::ComponentBase(config, context) {
23+
// Testsuite test should finish until 120s end
24+
engine::InterruptibleSleepFor(std::chrono::seconds(120));
25+
26+
if (!engine::current_task::ShouldCancel()) {
27+
throw std::runtime_error("Seems like testsuite test is still running, a bug in test?");
28+
}
29+
}
30+
};
31+
32+
int main(int argc, char* argv[]) {
33+
const auto component_list = components::MinimalServerComponentList().Append<SlowComponent>();
34+
return utils::DaemonMain(argc, argv, component_list);
35+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
components_manager:
2+
components:
3+
slow-component:
4+
server:
5+
listener:
6+
port: 8089
7+
task_processor: main-task-processor
8+
logging:
9+
fs-task-processor: main-task-processor
10+
loggers:
11+
default:
12+
file_path: '@stderr'
13+
level: debug
14+
overflow_behavior: discard
15+
16+
task_processors:
17+
main-task-processor:
18+
worker_threads: 4
19+
20+
default_task_processor: main-task-processor
21+
fs_task_processor: main-task-processor
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest_plugins = ['pytest_userver.plugins.core']
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import asyncio
2+
import os
3+
import signal
4+
5+
TIMEOUT = 60
6+
7+
8+
async def wait_for_logfile(fname: str):
9+
for i in range(TIMEOUT):
10+
with open(fname) as ifile:
11+
for line in ifile.readlines():
12+
if 'Starting to catch signals' in line:
13+
# Yes, sleeping for sync. Yes, that's bad.
14+
# No, it is not possible to wait for a specific event.
15+
await asyncio.sleep(1)
16+
return
17+
18+
# not yet started, waiting
19+
await asyncio.sleep(1)
20+
21+
assert False, f'log file {fname} does not contain start log entry, has the service started?'
22+
23+
24+
async def test_boot_logs(
25+
service_binary,
26+
service_config_path_temp,
27+
service_env,
28+
create_daemon_scope,
29+
ensure_daemon_started,
30+
_service_logfile_path,
31+
):
32+
async def _checker(*, session, process) -> bool:
33+
return True
34+
35+
async with create_daemon_scope(
36+
args=[
37+
str(service_binary),
38+
'--config',
39+
str(service_config_path_temp),
40+
],
41+
health_check=_checker,
42+
env=service_env,
43+
) as scope:
44+
daemon = await ensure_daemon_started(scope)
45+
46+
await wait_for_logfile(_service_logfile_path)
47+
48+
os.remove(_service_logfile_path)
49+
assert not os.path.exists(_service_logfile_path)
50+
51+
daemon.process.send_signal(signal.SIGUSR1)
52+
53+
for i in range(TIMEOUT):
54+
if os.path.exists(_service_logfile_path):
55+
break
56+
await asyncio.sleep(1)
57+
else:
58+
assert False, f'log file {_service_logfile_path} is not reopened'

core/src/components/manager.cpp

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,7 @@ void Manager::TaskProcessorsStorage::WaitForAllTasksBlocking() const noexcept {
152152
}
153153
}
154154

155-
Manager::Manager(
156-
std::unique_ptr<ManagerConfig>&& config,
157-
std::chrono::steady_clock::time_point start_time,
158-
const ComponentList& component_list
159-
)
155+
Manager::Manager(std::unique_ptr<ManagerConfig>&& config, std::chrono::steady_clock::time_point start_time)
160156
: config_(std::move(config)),
161157
task_processors_storage_(std::make_shared<
162158
engine::impl::TaskProcessorPools>(config_->coro_pool, config_->event_thread_pool)),
@@ -219,15 +215,18 @@ Manager::Manager(
219215
}
220216

221217
default_task_processor_ = default_task_processor_it->second.get();
222-
RunInCoro(*default_task_processor_, [this, &component_list]() { CreateComponentContext(component_list); });
223-
224-
if (!config_->disable_phdr_cache) {
225-
engine::impl::InitPhdrCache();
226-
}
218+
}
227219

228-
LOG_INFO()
229-
<< "Started components manager. All the components have started "
230-
"successfully.";
220+
engine::TaskWithResult<void> Manager::StartComponentSystem(const ComponentList& component_list) {
221+
return engine::CriticalAsyncNoSpan(*default_task_processor_, [this, &component_list]() {
222+
CreateComponentContext(component_list);
223+
if (!config_->disable_phdr_cache) {
224+
engine::impl::InitPhdrCache();
225+
}
226+
LOG_INFO()
227+
<< "Started components manager. All the components have started "
228+
"successfully.";
229+
});
231230
}
232231

233232
Manager::~Manager() {
@@ -325,6 +324,10 @@ void Manager::CreateComponentContext(const ComponentList& component_list) {
325324
component_context_ = std::make_unique<impl::ComponentContextImpl>(*this, std::move(loading_components));
326325

327326
AddComponents(component_list);
327+
328+
LOG_INFO()
329+
<< "Started components manager. All the components have started "
330+
"successfully.";
328331
}
329332

330333
components::ComponentConfigMap Manager::MakeComponentConfigMap(const ComponentList& component_list) {

core/src/components/manager.hpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@
1414

1515
USERVER_NAMESPACE_BEGIN
1616

17-
namespace engine::impl {
17+
namespace engine {
18+
template <typename T>
19+
class TaskWithResult;
20+
21+
namespace impl {
1822
class TaskProcessorPools;
19-
} // namespace engine::impl
23+
} // namespace impl
24+
} // namespace engine
2025

2126
namespace os_signals {
2227
class ProcessorComponent;
@@ -38,13 +43,10 @@ using TaskProcessorsMap = utils::impl::TransparentMap<std::string, std::unique_p
3843
// to the outside world.
3944
class Manager final {
4045
public:
41-
Manager(
42-
std::unique_ptr<ManagerConfig>&& config,
43-
std::chrono::steady_clock::time_point start_time,
44-
const ComponentList& component_list
45-
);
46+
Manager(std::unique_ptr<ManagerConfig>&& config, std::chrono::steady_clock::time_point start_time);
4647
~Manager();
4748

49+
engine::TaskWithResult<void> StartComponentSystem(const ComponentList& component_list);
4850
const ManagerConfig& GetConfig() const;
4951
const std::shared_ptr<engine::impl::TaskProcessorPools>& GetTaskProcessorPools() const;
5052
const TaskProcessorsMap& GetTaskProcessorsMap() const;

core/src/components/run.cpp

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <userver/crypto/openssl.hpp>
1414
#include <userver/dynamic_config/snapshot.hpp>
1515
#include <userver/dynamic_config/value.hpp>
16+
#include <userver/engine/exception.hpp>
17+
#include <userver/engine/task/task_with_result.hpp>
1618
#include <userver/formats/json/serialize.hpp>
1719
#include <userver/fs/blocking/read.hpp>
1820
#include <userver/logging/impl/mem_logger.hpp>
@@ -183,6 +185,33 @@ ManagerConfig ParseManagerConfigAndSetupLogging(
183185
}
184186
}
185187

188+
void CatchSignalsLoop(impl::Manager& manager, RunMode run_mode, utils::SignalCatcher& signal_catcher) noexcept {
189+
if (run_mode == RunMode::kOnce) {
190+
return;
191+
}
192+
193+
LOG_INFO() << "Starting to catch signals";
194+
for (;;) {
195+
auto signum = signal_catcher.Catch();
196+
if (signum == SIGTERM || signum == SIGQUIT) {
197+
break;
198+
} else if (signum == SIGINT) {
199+
if (IsTraced()) {
200+
// SIGINT is masked and cannot be used
201+
std::raise(SIGTRAP);
202+
} else {
203+
break;
204+
}
205+
} else if (signum == SIGUSR1 || signum == SIGUSR2) {
206+
LOG_INFO() << "Signal caught: " << utils::strsignal(signum);
207+
manager.OnSignal(signum);
208+
} else {
209+
LOG_WARNING() << "Got unexpected signal: " << signum << " (" << utils::strsignal(signum) << ')';
210+
UASSERT_MSG(false, "unexpected signal");
211+
}
212+
}
213+
}
214+
186215
void DoRun(
187216
const PathOrConfig& config,
188217
const std::optional<std::string>& config_vars_path,
@@ -217,34 +246,28 @@ void DoRun(
217246
PreheatStacktraceCollector();
218247
}
219248

220-
manager.emplace(std::make_unique<ManagerConfig>(std::move(manager_config)), start_time, component_list);
221-
} catch (const std::exception& ex) {
222-
LOG_ERROR() << "Loading failed: " << ex;
223-
throw;
224-
}
249+
manager.emplace(std::make_unique<ManagerConfig>(std::move(manager_config)), start_time);
225250

226-
if (run_mode == RunMode::kOnce) {
227-
return;
228-
}
251+
// Start component system in background.
252+
// POSIX signals can be already handled while component system is loading.
253+
auto start_components_task = manager->StartComponentSystem(component_list);
229254

230-
for (;;) {
231-
auto signum = signal_catcher.Catch();
232-
if (signum == SIGTERM || signum == SIGQUIT) {
233-
break;
234-
} else if (signum == SIGINT) {
235-
if (IsTraced()) {
236-
// SIGINT is masked and cannot be used
237-
std::raise(SIGTRAP);
238-
} else {
239-
break;
240-
}
241-
} else if (signum == SIGUSR1 || signum == SIGUSR2) {
242-
LOG_INFO() << "Signal caught: " << utils::strsignal(signum);
243-
manager->OnSignal(signum);
244-
} else {
245-
LOG_WARNING() << "Got unexpected signal: " << signum << " (" << utils::strsignal(signum) << ')';
246-
UASSERT_MSG(false, "unexpected signal");
255+
// The main event loop.
256+
// Other threads handle coroutines.
257+
CatchSignalsLoop(*manager, run_mode, signal_catcher);
258+
259+
if (run_mode == RunMode::kNormal) {
260+
start_components_task.RequestCancel();
261+
}
262+
263+
try {
264+
start_components_task.BlockingWait();
265+
start_components_task.Get();
266+
} catch (const engine::WaitInterruptedException&) {
247267
}
268+
} catch (const std::exception& ex) {
269+
LOG_ERROR() << "Loading failed: " << ex;
270+
throw;
248271
}
249272
}
250273

0 commit comments

Comments
 (0)