diff --git a/core/include/userver/utils/periodic_task.hpp b/core/include/userver/utils/periodic_task.hpp index 22ba3147ae93..02751c7e8ee5 100644 --- a/core/include/userver/utils/periodic_task.hpp +++ b/core/include/userver/utils/periodic_task.hpp @@ -124,6 +124,11 @@ class PeriodicTask final { /// PeriodicTask::Start() calls engine::current_task::GetTaskProcessor() /// to get the TaskProcessor. engine::TaskProcessor* task_processor{nullptr}; + + /// @brief enabled allows to enable/disable timer during run + /// SynchronizeDebug does not run the iteration on enabled=false, because its logic is just to imitate the passing of time + /// ForceStepAsync runs the iteration even for enabled=false + bool enabled{true}; }; /// Signature of the task to be executed each period. @@ -170,6 +175,7 @@ class PeriodicTask final { /// @note If 'ForceStepAsync' is called multiple times while Step() is /// being executed, all events will be conflated (one extra Step() call will /// be executed). + /// Runs the iteration even for enabled=false void ForceStepAsync(); /// Force next DoStep() iteration. It is guaranteed that there is at least one @@ -181,6 +187,7 @@ class PeriodicTask final { /// @returns true if task was successfully executed. /// @note On concurrent invocations, the task is guaranteed to be invoked /// serially, one time after another. + /// Does not run the iteration on enabled=false, because its logic is just to imitate the passing of time bool SynchronizeDebug(bool preserve_span = false); /// Skip Step() calls from loop until ResumeDebug() is called. If DoStep() diff --git a/core/src/utils/periodic_task.cpp b/core/src/utils/periodic_task.cpp index da947d48e3f8..c7b2e2a863a4 100644 --- a/core/src/utils/periodic_task.cpp +++ b/core/src/utils/periodic_task.cpp @@ -22,8 +22,8 @@ namespace { auto TieSettings(const PeriodicTask::Settings& settings) { // Can't use Boost.Pfr, because Settings has custom constructors. - const auto& [f1, f2, f3, f4, f5, f6] = settings; - return std::tie(f1, f2, f3, f4, f5, f6); + const auto& [f1, f2, f3, f4, f5, f6, f7] = settings; + return std::tie(f1, f2, f3, f4, f5, f6, f7); } } // namespace @@ -98,6 +98,7 @@ void PeriodicTask::Stop() noexcept { } void PeriodicTask::SetSettings(Settings settings) { + bool should_notify_task{}; { auto writer = settings_.StartWrite(); @@ -106,7 +107,7 @@ void PeriodicTask::SetSettings(Settings settings) { return; } settings.flags = writer->flags; - should_notify_task = settings.period != writer->period || settings.exception_period != writer->exception_period; + should_notify_task = settings.period != writer->period || settings.exception_period != writer->exception_period || settings.enabled != writer->enabled; *writer = std::move(settings); writer.Commit(); } @@ -134,8 +135,10 @@ bool PeriodicTask::IsRunning() const { return task_.IsValid(); } void PeriodicTask::Run() { bool skip_step = false; + bool task_enabled = true; { auto settings = settings_.Read(); + task_enabled = settings->enabled; if (!(settings->flags & Flags::kNow)) { skip_step = true; } @@ -145,11 +148,12 @@ void PeriodicTask::Run() { const auto before = std::chrono::steady_clock::now(); bool no_exception = true; - if (!std::exchange(skip_step, false)) { + if (!std::exchange(skip_step, false) && task_enabled) { no_exception = Step(); } - const auto settings = settings_.Read(); + auto settings = settings_.Read(); + task_enabled = settings->enabled; auto period = settings->period; const auto exception_period = settings->exception_period.value_or(period); @@ -162,12 +166,19 @@ void PeriodicTask::Run() { start = std::chrono::steady_clock::now(); } - while (changed_event_.WaitForEventUntil(start + MutatePeriod(period))) { + engine::Deadline deadline; + while (deadline = task_enabled ? engine::Deadline::FromTimePoint(start + MutatePeriod(period)) : engine::Deadline{}, changed_event_.WaitForEventUntil(deadline)) { if (should_force_step_.exchange(false)) { - break; + break; } // The config variable value has been changed, reload const auto settings = settings_.Read(); + if(task_enabled != settings->enabled) + { + task_enabled = settings->enabled; + break; + } + period = settings->period; const auto exception_period = settings->exception_period.value_or(period); if (!no_exception) period = exception_period; diff --git a/core/src/utils/periodic_task_test.cpp b/core/src/utils/periodic_task_test.cpp index fba863a8fd20..a070290358c8 100644 --- a/core/src/utils/periodic_task_test.cpp +++ b/core/src/utils/periodic_task_test.cpp @@ -418,4 +418,39 @@ UTEST_F(PeriodicTaskLog, ErrorLog) { task.Stop(); } +UTEST(PeriodicTask, EnableDisable) { + SimpleTaskData simple; + + constexpr auto period = 3ms; + utils::PeriodicTask::Settings settings(period); + + constexpr Count n = 10; + + const auto start = std::chrono::steady_clock::now(); + utils::PeriodicTask task("task", period, simple.GetTaskFunction()); + EXPECT_TRUE(simple.WaitFor(period * n * kSlowRatio, [&simple]() { return simple.GetCount() > 5; })); + auto countBeforeStop = simple.GetCount(); + settings.enabled = false; + task.SetSettings(settings); + EXPECT_FALSE(simple.WaitFor(period * n * kSlowRatio, [&simple]() { return simple.GetCount() > 10; })); + EXPECT_TRUE(countBeforeStop == simple.GetCount()); + EXPECT_TRUE(task.IsRunning()); + settings.enabled = true; + task.SetSettings(settings); + EXPECT_TRUE(simple.WaitFor(period * n * kSlowRatio, [&simple,countBeforeStop]() { return simple.GetCount() > 10; })); + EXPECT_TRUE(task.IsRunning()); + auto countBeforeStop2 = simple.GetCount(); + settings.enabled = false; + task.SetSettings(settings); + EXPECT_FALSE(simple.WaitFor(period * n * kSlowRatio, [&simple]() { return simple.GetCount() > 20; })); + EXPECT_TRUE(countBeforeStop2 == simple.GetCount()); + EXPECT_TRUE(task.IsRunning()); + + task.ForceStepAsync(); + EXPECT_TRUE(simple.WaitFor(period * n * kSlowRatio, [&simple]() { return simple.GetCount() > 30; })); + + task.Stop(); + EXPECT_TRUE(!task.IsRunning()); +} + USERVER_NAMESPACE_END